From fed5161fc6e95d48e84d2cb0a783d219fd45ebc3 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 14 Mar 2022 09:26:39 +0100 Subject: [PATCH 001/992] Translated using Weblate (Polish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 23.0% (15 of 65 strings) Translated using Weblate (Russian) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Bosnian) Currently translated at 4.6% (3 of 65 strings) Translated using Weblate (Bosnian) Currently translated at 19.7% (122 of 617 strings) Translated using Weblate (Croatian) Currently translated at 98.2% (606 of 617 strings) Translated using Weblate (Japanese) Currently translated at 99.5% (614 of 617 strings) Translated using Weblate (Serbian) Currently translated at 94.9% (586 of 617 strings) Translated using Weblate (Sardinian) Currently translated at 99.6% (615 of 617 strings) Translated using Weblate (Italian) Currently translated at 99.5% (614 of 617 strings) Translated using Weblate (German) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Estonian) Currently translated at 99.6% (615 of 617 strings) Translated using Weblate (Tamil) Currently translated at 55.2% (341 of 617 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Hebrew) Currently translated at 99.8% (616 of 617 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Greek) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Norwegian Bokmål) Currently translated at 94.4% (583 of 617 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (65 of 65 strings) Translated using Weblate (Japanese) Currently translated at 10.7% (7 of 65 strings) Translated using Weblate (Polish) Currently translated at 100.0% (617 of 617 strings) Translated using Weblate (Ukrainian) Currently translated at 99.5% (614 of 617 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (615 of 615 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (615 of 615 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (615 of 615 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 99.6% (613 of 615 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (613 of 613 strings) Added translation using Weblate (Bosnian) Co-authored-by: Agnieszka C Co-authored-by: Ajeje Brazorf Co-authored-by: Allan Nordhøy Co-authored-by: Andrij Mizyk Co-authored-by: Emin Tufan Çetin Co-authored-by: GnuPGを使うべきだ Co-authored-by: GobinathAL Co-authored-by: Hosted Weblate Co-authored-by: Ihor Hordiichuk Co-authored-by: Issa1553 Co-authored-by: Jeff Huang Co-authored-by: Jonatan Nyberg Co-authored-by: Priit Jõerüüt Co-authored-by: Rex_sa Co-authored-by: Ricardo Co-authored-by: S3aBreeze Co-authored-by: SC Co-authored-by: Terry Louwers Co-authored-by: Vasilis K Co-authored-by: Vitor Henrique Co-authored-by: Yaron Shahrabani Co-authored-by: chr56 Co-authored-by: luciana Co-authored-by: nautilusx Co-authored-by: pjammo Co-authored-by: sasukeiscool Co-authored-by: Óscar Fernández Díaz Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bs/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ja/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/ Translation: NewPipe/Metadata --- app/src/main/res/values-ar/strings.xml | 4 + .../main/res/values-b+zh+HANS+CN/strings.xml | 4 + app/src/main/res/values-bs/strings.xml | 124 ++++++++++++++++++ app/src/main/res/values-de/strings.xml | 6 +- app/src/main/res/values-el/strings.xml | 4 + app/src/main/res/values-es/strings.xml | 16 ++- app/src/main/res/values-et/strings.xml | 4 +- app/src/main/res/values-he/strings.xml | 3 + app/src/main/res/values-hr/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 7 +- app/src/main/res/values-ja/strings.xml | 3 +- app/src/main/res/values-nb-rNO/strings.xml | 3 + app/src/main/res/values-nl/strings.xml | 4 + app/src/main/res/values-pl/strings.xml | 15 ++- app/src/main/res/values-pt-rBR/strings.xml | 4 + app/src/main/res/values-pt/strings.xml | 4 + app/src/main/res/values-ru/strings.xml | 4 + app/src/main/res/values-sc/strings.xml | 4 + app/src/main/res/values-sr/strings.xml | 1 + app/src/main/res/values-sv/strings.xml | 6 +- app/src/main/res/values-ta/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 18 ++- app/src/main/res/values-uk/strings.xml | 7 +- app/src/main/res/values-zh-rTW/strings.xml | 4 + .../metadata/android/bs/changelogs/63.txt | 8 ++ .../metadata/android/bs/full_description.txt | 1 + .../metadata/android/bs/short_description.txt | 1 + .../metadata/android/ja/short_description.txt | 2 +- .../metadata/android/pt-BR/changelogs/984.txt | 7 + .../android/pt-BR/short_description.txt | 2 +- .../android/zh-Hans/changelogs/984.txt | 7 + 31 files changed, 251 insertions(+), 30 deletions(-) create mode 100644 app/src/main/res/values-bs/strings.xml create mode 100644 fastlane/metadata/android/bs/changelogs/63.txt create mode 100644 fastlane/metadata/android/bs/full_description.txt create mode 100644 fastlane/metadata/android/bs/short_description.txt create mode 100644 fastlane/metadata/android/pt-BR/changelogs/984.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/984.txt diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index d25b478ef..8f085e64a 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -736,4 +736,8 @@ يتم تشغيله في الخلفية تعليق مثبت LeakCanary غير متوفر + ضبط الصوت من خلال النغمات الموسيقية النصفية + خطوة الإيقاع + الافتراضي ExoPlayer + تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل. \ No newline at end of file diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index d9f874de8..0f18808a6 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -676,4 +676,8 @@ 已经在后台播放 置顶评论 LeakCanary 不可用 + 以音乐半音调整音高 + 节奏步长 + 改变加载间隔的大小(当前%s),较低的值可以加快初始的视频加载速度,改变需要重启播放器。 + ExoPlayer 默认 \ No newline at end of file diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml new file mode 100644 index 000000000..e865f3c22 --- /dev/null +++ b/app/src/main/res/values-bs/strings.xml @@ -0,0 +1,124 @@ + + + Pratnje + Dodirnite lupu da bi ste započeli. + Instališi + Otkaži + Objavljeno na %1$s + Otvorite u skočnom prozoru + Koristite vanjski video pokretač + U redu + Otvorite u pretraživaču + Označite kao pregledano + Otvorite sa + Podijeli + Preuzimanje + Pozadina + Pretraži + Podešavanja + Jeste li mislili \"%1$s\"\? + Prikažite informaciju + Podijelite pomoću + Pokazuju se rezultati za: %s + Otkažite pratnju + Uklanja zvuk u nekim režimima + Koristite eksterni audio pokretač + Pratite + U pratnji + Otkazana pratnja kanala + Nije moguće promijeniti pratnju + Nije moguće ažurirati pratnju + Instalirajte nedostajeću Kore aplikaciju\? + Obilježeni Popisi + Iskačni prozor + Izaberite Podprozor + Dodajte u + Samo neki uređaji mogu 2K/4K video zapise reproducirati + Prikaži veće režime + Prikažite postavku \"Pokreni sa KODI-jem\" + Fascikla za preuzimanje audia + Skinuti audio zapisi su ovdje spremljeni + Odaberite fasciklu gdje se audio datoteke skidaju + Zadani režim + Zadani režim za iskačući prozor + Prekinite pokretač + Prikažite postavku da biste video preko KODI medijskog centra video pokrenuli + Skalirajte sličicu na 1:1 omjer + Prvo radno dugme + Skalirajte video sličicu prikazanu u obavijesti sa 16:9 na 1:1 omjer (može uvesti poremećaje) + Drugo radno dugme + Treće radno dugme + Četvrto radno dugme + Peto radno dugme + Možete najviše tri radnje odabrati za prikaz u kompaktnom obavještaju! + Ponovi + Pomiješajte + Uredite svaku radnju obavještenja ispod pri dodiru na nju. Odaberite bar tri od njih za prikaz u kompaktnom obavještenju koristeći potvrdne okvire s desne strane + Ništa + Obojite obavještenje + Učitavanje + Zadani zvučni format + Dajte Android-u da prilagodi boju obavijesti prema glavnoj boji na sličici (čuvajte u umu da ovo nije dostupno na svim uređajima) + Zvuk + Zadani video format + Tema + Noćna Tema + Svijetla + Tamna + Zapamtite podešavanja za iskočne prozore + Crna + Zapamtite posljednju veličinu i položaj iskočnog prozora + Koristite brzo neprecizno premotavanje + Neprecizno premotavanje dozvoljava pokretaču brže premotavanje s gorom preciznošću. Premotavanje za 5, 15 ili 25 sekundi ne radi s ovim + Promijenite veličinu intervala za učitavanje (trenutačno %s). Niža vrijednost bi vam moglo ubrzat učitavanje videa. Trebate te ponovno učitati pokretač za promjenu. + Prebacivanje sa jednog pokretača na drugi bi van moglo zamijeniti pokretni red + Isključite da sakrijete komentare + Pitajte za potvrdu prije isčišćavanja reda + Isključite kako bi ste spriječili učitavanje sličica, sto če vam spasiti korištenje podataka i memorije. Promjene čiste slike oboje u memoriji i predmešiju na disku + Prikažite komentare + Pokažite \'Sljedeće\' i \'Slične\' video zapise + Prikažite opis + Prikažite meta-informaciju + Isključite da biste sakrili opis videa i dodatne informacije + Obrišite keširane metapodatke + Keširani metapodaci su obrisani + Isključite da bi ste sakrili meta-informacijske kutije sa dodatnim informacijama o praviocu prijenosa, sadržaju prijenosa ili zahtjevu za pretraživanje + Preuzmite datoteku za prijenos + Keširane slike su izbrisane + Nije pronađen nijedan medijski prijenosnik. Hoćete VLC instalisati\? + Nijedan medijski prijenosnik nije nađen na vašem uređaju (možete VLC instalisati da bi ste ga pokrenili). + Uklonite sve keširane podatke web stranica + Automatski sljedeći prijenos u red stavite + Pokretna kontrola zvučne glasnine + Nastavite završni (ne-ponavljajući) reprodukcijski red privlakom srodnog prijenosa + Automatsko redanje + Koristite pokrete za kontrolu jačine zvuka pokretača + Kontrola osvetljenja sa pokretima + Prijedlozi za pretragu + Koristite pokrete za kontrolu svjetline pokretača + Odaberite prijedloge koje želite prikazati prilikom pretrage + Lokalni prijedlozi za pretraživanje + Razdaljeni prijedlozi za pretraživanje + Povijest pretraga + Historija pregleda + Lokalno pohranite upite pretraživanja + Nastavi reproduciranje + Pozicije u listama + Prikažite indikatore pozicije reprodukcije u listama + Vratite posljednju poziciju reprodukcije + Raščistite podatke + Vodite računa o pregledanim video zapisima + Nastavite sa reprodukcijom + Nastavite reprodukciju nakon prekida (npr. telefonskih poziva) + Preuzimanje + Automatsko reproduciranje + Pokrenite glavni pokretač preko cijelog ekrana + Nemojte pokretati video zapise u mini-pokretaču, već direktno pređite na režim cijelog ekrana, ako je automatska rotacija zaključana. I dalje možete pristupiti mini-pokretaču pri izlasku iz cijelog ekrana + Fascikla za preuzimanje videa + Skinuti video zapisi su ovdje spremljeni + Odaberite fasciklu gdje se video datoteke skidaju + Pokrenite s KODI-jem + Vrijeme premotavanja naprijed/nazad + Aktivni pokretni red će biti zamijenjen + Učitajte sličice + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0721ebfad..d49acf028 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -329,7 +329,7 @@ Für die Wiedergabe im Hintergrund minimieren Für die Wiedergabe im Pop-up minimieren Vorspulen bei Stille - Schritt + Stufe Zurücksetzen Kanäle Wiedergabelisten @@ -688,4 +688,8 @@ Wird bereits im Hintergrund abgespielt Angehefteter Kommentar LeakCanary ist nicht verfügbar + Tonhöhe nach musikalischen Halbtönen anpassen + Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players. + Geschwindigkeitsstufe + ExoPlayer Standard \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 5e7fd4940..d4a650a25 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -688,4 +688,8 @@ Αναπαράγεται ήδη στο παρασκήνιο Καρφιτσωμένο σχόλιο Το LeakCanary δεν είναι διαθέσιμο + Προσαρμόστε τον τόνο με βάση τα μουσικά ημιτόνια + Βήμα τέμπο + Εξ\' ορισμού ExoPlayer + Αλλάξτε το μέγεθος του διαστήματος φόρτωσης (επί του παρόντος είναι %s). Μια χαμηλότερη τιμή μπορεί να επιταχύνει την αρχική φόρτωση βίντεο. Οι αλλαγές απαιτούν επανεκκίνηση της εφαρμογής. \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 295fa22f8..d1a286f17 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -18,7 +18,7 @@ Reproducir con Kodi ¿Instalar la aplicación Kore que falta\? Mostrar opción «Reproducir con Kodi» - Mostrar opción para reproducir vídeo a través del centro de medios Kodi + Mostrar opción para reproducir un vídeo a través de Kodi Audio Formato de audio predefinido Descargar @@ -316,8 +316,8 @@ El proyecto NewPipe toma tu privacidad muy en serio. Por ello, la aplicación no recopila ningún dato sin tu consentimiento. \nLa política de privacidad de NewPipe explica en detalle qué datos se envían y almacenan al enviar un informe de fallo. Leer política de privacidad - Para cumplir con el «Reglamento general europeo de protección de datos (GDPR)», atraemos tu atención sobre la política de privacidad de NewPipe. Por favor léase cuidadosamente. -\nDebes aceptarlo para enviarnos el informe de error. + Para cumplir con el «Reglamento general europeo de protección de datos (GDPR)», atraemos su atención sobre la política de privacidad de NewPipe. Por favor léase cuidadosamente. +\nDebe aceptarlo para enviarnos el informe de error. Aceptar Declinar Sin límite @@ -367,11 +367,11 @@ Pausar descargas Mostrar error - No se puede crear el archivo + El archivo no puede ser creado No se puede crear la carpeta de destino No se pudo establecer una conexión segura No se pudo encontrar el servidor - No se puede conectar con el servidor + No se puede conectar al servidor El servidor no devolvio datos El servidor no acepta descargas multi-hilos, intente de nuevo con @string/msg_threads = 1 No encontrado @@ -565,7 +565,7 @@ Botón de tercera acción Botón de segunda acción Botón de primera acción - Escalar la miniatura del vídeo mostrada en la notificación de relación de aspecto 16:9 a 1:1 (puede ocasionar distorsiones) + Escalar la relación de aspecto de la miniatura del vídeo mostrada en la notificación de 16:9 a 1:1 (puede ocasionar distorsiones) Vaciar las cookies que NewPipe guarda al resolver un reCAPTCHA Mostrar contenido inapropiado para niños porque tiene un limite de edad (como 18+) Mostrar pérdidas de memoria @@ -691,4 +691,8 @@ Comentario fijado Ya se reproduce en segundo plano LeakCanary no está disponible + ExoPlayer valor por defecto + Paso de tempo + Cambia el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial del vídeo. Los cambios requieren un reinicio del reproductor. + Ajustar el tono por semitonos musicales \ No newline at end of file diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index c2fd8ef9e..83d959d3f 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -326,7 +326,7 @@ 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 tuleb sellega nõustuda. +\nMeile veateate saatmiseks pead sellega nõustuma. Minimeeri, kui kasutad teisi rakendusi Tegevus lülitusel peamiselt videopleierilt teisele rakendusele — %s Pole @@ -688,4 +688,6 @@ \nPalun paigalda nutiseadmesse failihaldur, mis järgib Storage Access Framework reeglistikku. Esiletõstetud kommentaar LeakCanary pole saadaval + ExoPlayer\'i vaikimisi väärtused + Muuda video laadimise välpa (hetkel %s). Väiksemast väärtusest võib abi olla, kui tahad et video esitamine algaks varem. Muudatuste jõustamine eeldab rakenduse uuesti käivitamist. \ 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 f3777c909..48a79282e 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -712,4 +712,7 @@ כבר מתנגן ברקע הערה ננעצה LeakCanary אינה זמינה + התאמת גובה הצליל לפי חצאי טונים מוזיקליים + צעד מקצב + ברירת מחדל של ExoPlayer \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 92e81f3c0..5c4f1210d 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -52,7 +52,7 @@ Odaberi prijedloge koji se prikazuju pri traženju Povijest pretraživanja Svaku pretragu spremi lokalno - Prati povijest + Pregledna Povijest Spremaj povijest gledanja Nastavi reprodukciju Nastavi reproducirati nakon prekidanja (npr. telefonski pozivi) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 90a9cc9c3..8c981a4d5 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -315,8 +315,8 @@ Il progetto NewPipe tiene molto alla tua privacy. Perciò, l\'app non raccoglie alcun dato senza il tuo consenso. \nL\'informativa sulla privacy spiega nel dettaglio quali dati vengono trattati e memorizzati durante l\'invio di segnalazioni per arresti anomali. Leggi l\'informativa sulla privacy - Per rispettare il regolamento europeo sulla protezione dei dati (GDPR), attiriamo la vostra attenzione riguardo l\'informativa sulla privacy di NewPipe. Si prega di leggerla attentamente. -\nDevi accettarla per inviarci il bug report. + Per rispettare il regolamento europeo sulla protezione dei dati (GDPR), richiamiamo la vostra attenzione sull\'informativa sulla privacy di NewPipe. Si prega di leggerla attentamente. +\nDeve essere accettata per poter inviare la segnalazione. Accetto Rifiuto Nessun limite @@ -537,7 +537,7 @@ Seleziona una playlist Risultati per: %s Pagina delle playlist - Prima di segnalare un problema, verificare che non sia già stato riportato da qualcun altro. Il tempo che gli sviluppatori utilizzerebbero per creare un ulteriore ticket potrebbe essere invece impiegato per correggere il problema stesso. + Prima di segnalare un problema, verificare che non sia già stato riportato da qualcun altro. Il tempo che gli sviluppatori utilizzerebbero per creare un ulteriore ticket, potrebbe invece essere impiegato per correggere il problema stesso. Segnala su GitHub Copia segnalazione formattata Mai @@ -688,4 +688,5 @@ Commento in primo piano Già in riproduzione in sottofondo LeakCanary non è disponibile + Regola il tono secondo i semitoni musicali \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 74a90752c..f69428ef7 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -324,7 +324,7 @@ 再生速度を変更 速度と音程を連動せずに変更 (歪むかもしれません) 無音の間に早送り - 音程幅 + 音階 登録解除 タブを選択 アプリの更新 @@ -676,4 +676,5 @@ エラー通知を作成 エラーを報告する通知 LeakCanaryが利用不可能です + 緩急音階 \ No newline at end of file diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 1735bd0c1..65b5550b3 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -684,4 +684,7 @@ Installer en filbehandler først, eller skru av «%s» i nedlastingsinnstillingene. Installer en filbehandler som støtter lagringstilgangsrammeverk først. LeakCanary er ikke tilgjengelig + ExoPlayer-forvalg + Juster toneart etter musikalske halvtoner + Tempo-steg \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index e9f20e620..1c5089ff4 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -688,4 +688,8 @@ \nInstalleer een bestandsbeheerder die compatibel is met het Storage Access Framework. Vastgemaakt commentaar LeakCanary is niet beschikbaar + Verander de laad interval tijd (nu %s). Een lagere waarde kan het initiële laden van de video versnellen. De wijziging vereist een herstart van de speler. + Pas de toonhoogte aan met muzikale halve tonen + Tempo stap + ExoPlayer standaard \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index f0e74e4b8..c79653f1c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -227,7 +227,7 @@ Wyeksportowano Zaimportowano Nieprawidłowy plik ZIP - Ostrzeżenie: Nie udało się zaimportować wszystkich plików. + Ostrzeżenie: Nie udało się zaimportować wszystkich plików Zastąpi to obecne ustawienia. Otwórz menu Zamknij menu @@ -242,8 +242,8 @@ Usuń z ulubionych Czy usunąć tę playlistę\? Playlista utworzona - Dodano do playlisty. - Miniatura playlisty zmieniona. + Dodano do playlisty + Miniatura playlisty zmieniona Bez napisów Dopasuj Wypełnij @@ -646,7 +646,7 @@ \nNewPipe nie będzie w stanie załadować tego kanału w przyszłości. \nCzy chcesz anulować subskrypcję tego kanału\? Nie udało się załadować kanału dla „%s”. - Począwszy od systemu Android 10, obsługiwany jest tylko systemowy selektor folderów (SAF) + Począwszy od Androida 10 obsługiwany jest tylko systemowy selektor folderów (SAF) Systemowy selektor folderów (SAF) nie jest obsługiwany przez system Android KitKat i niższy Zostaniesz zapytany(-na), gdzie zapisać każdy pobierany plik Nie ustawiono jeszcze folderu zapisywania, wybierz domyślny teraz @@ -697,7 +697,7 @@ Powiadomienie raportu o błędach Powiadomienia do zgłaszania błędów NewPipe napotkał błąd. Naciśnij, aby zgłosić - Wystąpił błąd. Zobacz powiadomienie. + Wystąpił błąd, zobacz powiadomienie Pokaż snackbar z błędem Utwórz powiadomienie o błędzie Nie znaleziono odpowiedniego menedżera plików dla tej akcji. @@ -707,4 +707,9 @@ Już jest odtwarzane w tle Przypięty komentarz LeakCanary jest niedostępne + Rozmiar interwału ładowania odtwarzania + Zmień rozmiar interwału ładowania (aktualnie %s). Niższa wartość może przyspieszyć początkowe ładowanie wideo. Zmiany wymagają ponownego uruchomienia odtwarzacza + domyślny ExoPlayera + Dostosuj wysokość półtonami + Krok tempa \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index a445bf9c1..1fd65392f 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -688,4 +688,8 @@ Já está tocando em segundo plano Comentário fixado O LeakCanary não está disponível + Passo do tempo + Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. As alterações exigem que o player reinicie. + Ajustar o tom por semitons musicais + ExoPlayer padrão \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 7d9f6f959..f40b8a478 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -688,4 +688,8 @@ Já está a reproduzir em segundo plano Comentário fixado LeakCanary não está disponível + Ajustar o tom por semitons musicais + Predefinido do ExoPlayer + Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar. + Passo do tempo \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index dd72474e7..e5785c685 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -704,4 +704,8 @@ Уже проигрывается в фоне Закреплённый комментарий LeakCanary недоступна + Регулировка высоты тона по музыкальным полутонам + Шаг темпа + Стандартное значение ExoPlayer + Изменить размер интервала загрузки (сейчас %s). Меньшее значение может ускорить начальную загрузку видео. Изменение значения потребует перезапуска плеера. \ No newline at end of file diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 599c4a97b..0262d0e96 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -688,4 +688,8 @@ Cummentu apicadu Giai in riprodutzione in s\'isfundu LeakCanary no est a disponimentu + Règula s\'intonatzione in base a sos semitonos musicales + Passu de su ritmu + Valore ExoPlayer predefinidu + Muda sa mannària de s\'intervallu de carrigamentu (in custu momentu %s). Unu valore prus bassu diat pòdere allestrare su carrigamentu de incumintzu de su vìdeu. Sas modìficas tenent bisòngiu de torrare a allùghere su riproduidore. \ No newline at end of file diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 639eb3171..f7b53ffe2 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -660,4 +660,5 @@ Приказуј указиваче слике Не покрећи видео у малом прозору, већ пређи одмах у пун приказ заслона, уколико је обртање приказа закључано. И даље можете приступити малом приказу извођача изласком из пуног приказа Покрени пуни главни приказ извођача + Прекините плејер \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 641722771..12353e775 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -159,7 +159,7 @@ © %1$s av %2$s under %3$s Om Licenser - Öppen och enkel Android app för mediastreaming. + Öppen och enkel Android-app för mediastreaming. Visa på GitHub NewPipes licens Vad du än har för idéer; översättningar, designändringar, kodstädning eller riktiga stora kodändringar—hjälp är alltid välkommen. Ju mer som görs desto bättre blir det! @@ -688,4 +688,8 @@ Spelas redan i bakgrunden Fäst kommentar LeakCanary är inte tillgänglig + Justera tonhöjden med musikaliska halvtoner + ExoPlayer standard + Ändra inläsningsintervallets storlek (för närvarande %s). Ett lägre värde kan påskynda den första videoinläsningen. Ändringar kräver omstart av spelaren. + Temposteg \ No newline at end of file diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 62368b84b..4701e7604 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -341,7 +341,7 @@ பதிவுத்தட விவரங்களைக் காட்டுவதில் பிழை அமைவையும் இறக்குமதி செய்யவா\? பின்னணியில் இயங்கத் துவங்கு - + பாப்அப்பில் இயங்கத் துவங்கு பதிவுத்தட விவரங்களைக் காட்டு ஒளிச்சரத்தைத் தேர்ந்தெடு ஒளிச்சரத்தில் சேர் diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 0263336c7..1b30f5c71 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -20,7 +20,7 @@ Ses indirme klasörü İndirilen ses dosyaları burada depolanır Ses dosyaları için indirme klasörünü seç - Varsayılan çözünürlük + Öntanımlı çözünürlük Kodi ile oynat Eksik Kore uygulaması yüklensin mi\? \"Kodi ile oynat\" seçeneğini göster @@ -239,7 +239,7 @@ Yakınlaştır Hata ayıklama Kendiliğinden oluşturulan - Bellek sızıntısı izleme, yığın boşaltımı sırasında uygulamanın yanıt vermemesine neden olabilir + Bellek sızıntısı izleme, yığın boşaltımı sırasında uygulamanın yanıtlamamasına neden olabilir Yaşam döngüsü dışı hatalarını bildir Parçanın dışında veya atımdan sonraki etkinlik yaşam döngüsündeki teslim edilemeyen Rx beklentilerinin bildirimini zorla Hızlı isabetsiz konumlama kullan @@ -344,10 +344,10 @@ Kayıtlı sekmeler okunamadı, bu nedenle öntanımlılar kullanılıyor Öntanımlıları geri yükle Öntanımlıları geri yüklemek istiyor musunuz\? - Abone sayısı mevcut değil + Abone sayısı yok Ana sayfada hangi sekmeler gösterilir Güncellemeler - Yeni bir sürüm mevcut olduğunda uygulama güncellemesi için bir bildirim göster + Yeni sürüm olduğunda uygulama güncellemesi için bildirim göster Liste görünümü kipi Liste Izgara @@ -358,8 +358,8 @@ durduruldu sırada son işlemler uygulanıyor - Sıra - İşlem sistem tarafından reddedildi + Kuyruğa al + İşlem sistemce reddedildi İndirme başarısız Benzersiz ad oluştur Üzerine yaz @@ -687,5 +687,9 @@ Oynatıcıyı çöktür Zaten arka planda oynuyor Sabitlenmiş yorum - LeakCanary mevcut değil + LeakCanary yok + Yükleme ara boyutunu değiştir (şu anda %s). Düşük bir değer videonun ilk yüklenişini hızlandırabilir. Değişiklikler oynatıcının yeniden başlatılmasını gerektirir. + ExoPlayer öntanımlısı + Tempo adımı + Perdeyi müzikal yarım tonlarla uyarla \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 4a47fe100..4f3144a8d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -58,6 +58,7 @@ Завантаження Звіт про помилку Усе + Так Вимкнено Збій застосунку/інтерфейсу Ваш коментар (англійською): @@ -335,7 +336,7 @@ Зменшити при перемиканні застосунку Дія при перемиканні до інших застосунків з основного відеопрогравача — %s Нічого - Зменшити до програвача у фоновому режимі + Згорнути у фоновий програвач Зменшити до програвача у вікні Канали Добірки @@ -703,4 +704,8 @@ Уже відтворюється у фоновому режимі Закріплений коментар LeakCanary недоступний + Крок темпу + Регулювання висоти звуку за музичними півтонами + Типовий ExoPlayer + Змінити розмір інтервалу завантаження (наразі %s). Менше значення може прискорити початкове завантаження відео. Зміни вимагають перезапуску програвача. \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 53dc34025..b4c8cc8e8 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -676,4 +676,8 @@ 釘選留言 已經在背景播放 LeakCanary 無法使用 + 步進時間 + ExoPlayer 預設值 + 變更載入間隔大小(目前為 %s)。較低的值可能會提昇初始影片載入速度。變更需要重新啟動播放器。 + 按音樂半音調整音高 \ No newline at end of file diff --git a/fastlane/metadata/android/bs/changelogs/63.txt b/fastlane/metadata/android/bs/changelogs/63.txt new file mode 100644 index 000000000..ea2c38ae7 --- /dev/null +++ b/fastlane/metadata/android/bs/changelogs/63.txt @@ -0,0 +1,8 @@ +### Poboljšanja +- Postavke za uvoz/izvoz #1333 +- Smanjite prekoravanje (poboljšanje performansi) #1371 +- Mala poboljšanja koda #1375 +- Dodaj sve o GDPR #1420 + +### Popravljeno +- Skidač: Popravljen pad tokom skidanja nedovršenih preuzimanja iz .giga datoteka # 1407 diff --git a/fastlane/metadata/android/bs/full_description.txt b/fastlane/metadata/android/bs/full_description.txt new file mode 100644 index 000000000..75309fee4 --- /dev/null +++ b/fastlane/metadata/android/bs/full_description.txt @@ -0,0 +1 @@ +NewPipe ne koristi nikakve Google framework biblioteke, ili YouTube API. Parsi samo web stranicu da bi stekla informaciju koja joj treba. S time se ova aplikacija može koristiti na uređajima bez instalisanih Google Servisa. Također vam ni ne treba YouTube račun da koristite NewPipe, a i FLOSS je. diff --git a/fastlane/metadata/android/bs/short_description.txt b/fastlane/metadata/android/bs/short_description.txt new file mode 100644 index 000000000..acc8b70e1 --- /dev/null +++ b/fastlane/metadata/android/bs/short_description.txt @@ -0,0 +1 @@ +Jednostavna i besplatna zamjena za YouTube na Android-u. diff --git a/fastlane/metadata/android/ja/short_description.txt b/fastlane/metadata/android/ja/short_description.txt index 6ed025d04..b133ff472 100644 --- a/fastlane/metadata/android/ja/short_description.txt +++ b/fastlane/metadata/android/ja/short_description.txt @@ -1 +1 @@ -Androidのフリーで軽量なYouTubeアプリ。 +Androidの無料で軽量なYouTubeアプリ。 diff --git a/fastlane/metadata/android/pt-BR/changelogs/984.txt b/fastlane/metadata/android/pt-BR/changelogs/984.txt new file mode 100644 index 000000000..6ec48e050 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/984.txt @@ -0,0 +1,7 @@ +Carregamento de itens iniciais suficientes em listas para preencher a tela inteira e corrigir a rolagem em tablets e TVs +Correção de falhas aleatórias ao percorrer listas +Procura rápida no player do arco overlay passandndo pela UI do sistema +Reversão das mudanças para recortes ao jogar em várias janelas, causando a regressão do jogador deslocado em alguns telefones +Aumento do compileSdk de 30 para 31 +Atualização da biblioteca de relatórios de erros +Refatoração de algum código no player diff --git a/fastlane/metadata/android/pt-BR/short_description.txt b/fastlane/metadata/android/pt-BR/short_description.txt index 66968d96d..1f2c45723 100644 --- a/fastlane/metadata/android/pt-BR/short_description.txt +++ b/fastlane/metadata/android/pt-BR/short_description.txt @@ -1 +1 @@ -Um app de fonte aberta gratuito e leve do YouTube para Android. +Um frontend gratuito e leve do YouTube para Android. diff --git a/fastlane/metadata/android/zh-Hans/changelogs/984.txt b/fastlane/metadata/android/zh-Hans/changelogs/984.txt new file mode 100644 index 000000000..93706b781 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/984.txt @@ -0,0 +1,7 @@ +在列表中加载足够多的初始项目以填满整个屏幕,以修复平板电脑和电视上的滚动问题 +修复滚动列表时随机崩溃问 +让播放器的快速寻址线位于系统UI之下 +恢复在多窗口播放时对切口的改变,在某些手机上造成播放器错位 +将compileSdk从30提升到31 +更新错误报告库 +重构部分播放器代码 From f5ce22805935a9669ee0db4ab74d4d8d5ca9e305 Mon Sep 17 00:00:00 2001 From: Dar9586 Date: Mon, 14 Mar 2022 15:53:42 +0000 Subject: [PATCH 002/992] Translated using Weblate (Italian) Currently translated at 99.8% (616 of 617 strings) --- app/src/main/res/values-it/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 8c981a4d5..77b4428b0 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -689,4 +689,6 @@ Già in riproduzione in sottofondo LeakCanary non è disponibile Regola il tono secondo i semitoni musicali + ExoPlayer predefinito + Cambia la dimensione dell\'intervallo da caricare (attualmente %s). Un basso valore potrebbe velocizzare il caricamento iniziale del video. Il cambiamento richiede un riavvio del player. \ No newline at end of file From 06227d4514e2ea7f9605b8ea63b1e1fa6072b1bd Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 14 Mar 2022 15:46:01 +0000 Subject: [PATCH 003/992] Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 89.3% (551 of 617 strings) --- app/src/main/res/values-zh-rHK/strings.xml | 90 ++++++++++++++++++---- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 233174f06..aa1737ddd 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -28,13 +28,13 @@ 唔支援呢個網址 內容預設語言 影音 - 外觀 + 版面 幕後播緊 網絡問題 播放影片,片長: 上載者嘅頭像縮圖 讚好 - 討厭 + 劣評 搵唔到串流播放器。要唔要裝 VLC? 喺瀏覽器開啟 分享影片 @@ -55,7 +55,7 @@ 出咗啲問題,唔好意思。 報告 資訊: - 事發經過: + 出事: 您嘅意見 (請以英文輸入): 詳情: 影片 @@ -71,33 +71,34 @@ 校驗碼 檔案名稱 - 線程 + 執行緒數量 錯誤 NewPipe 下載緊 撳一下睇詳情 請等等… 複製咗去剪貼簿 請之後喺設定度揀定一個下載資料夾 - 在畫中畫模式開啟 - 預設畫中畫解像度 + 以浮面模式開啟 + 預設浮面解像度 顯示更高解像度 得某啲機至播到 2K/4K 影片 預設影片檔案格式 純黑 - 以畫中畫模式播放 + 浮面播緊 所有 App/界面閃退 經過:\\n請求:\\n內容語言:\\n內容國家:\\nApp 語言:\\n服務:\\nGMT 時間:\\n封裝:\\n版本:\\nOS 版本: 百萬 - reCAPTCHA 挑戰 - 畫中畫模式需要此權限 - 需完成 reCAPTCHA 挑戰 + reCAPTCHA 考驗 + 以浮面模式開啟 +\n有呢個權限至得 + 要完成 reCAPTCHA 考驗 某啲解像度可能會冇聲 幕後播 浮面播 - 記住畫中畫大小及位置 - 記住最近設定的畫中畫大小及位置 + 記住浮面點擺法 + 記住對上一次浮面嘅大細同擺位 搜尋建議 揀選搜尋嘅時候顯示邊啲建議 不適用 @@ -112,7 +113,7 @@ 喺 Android 上盡享自由輕便串流。 檢視我們的 GitHub NewPipe 嘅授權協議 - 無論您僅想分享您對 NewPipe 的一些構思,還是願意設計和翻譯程式介面,甚至想幫我們整理或重新編寫原始碼,我們都無任歡迎。貢獻更多,應用程式便會變得更好! + 無論您對翻譯、設計改動、打掃程式碼,抑或開山劈石編寫程式碼有咩偈仔,都無任歡迎您幫手。聚沙成塔,眾志成城! 檢閱特許 貢獻 抹走睇過嘅紀錄 @@ -126,7 +127,7 @@ 更新唔到訂閱 顯示資訊 訂閱 - 已收藏播放清單 + 收起嘅播放清單 用粗略快轉 加入去 選擇標籤 @@ -397,7 +398,7 @@ 執好就撳一下「搞掂」 靜音 處理緊… 可能要等等 - 使播放器崩潰 + 令播放器閃退 自動輪候 係咪要全部剷走晒睇過嘅紀錄? 顯示元資訊 @@ -409,7 +410,7 @@ 抹除元資料快取 抹除咗元資料快取 自動輪候接續落串流 - 排隊播要完 (又未設定循環播放) 嘅時候就追加一條啱上下嘅串流 + 排隊播要完嘅時候 (又未設定循環播放) 就追加一條咁上下嘅串流 PeerTube 站 去 %s 發掘啱您心水嘅站 播放清單 @@ -530,4 +531,61 @@ 喺影片「詳情:」度撳一下「幕後播」或者「浮面播」個掣嘅時候顯示提示 紀錄 紀錄 + 攝下個等陣播 + 記憶體洩露監測喺傾卸堆疊嘅時候,或者會導致個 app 冇反應 + 閂選單 + 攝咗做下個等陣播 + 點做法 + 影片雜湊進度嘅通知 + 大笪地 + 抹走搜尋紀錄 + 刪除搜尋關鍵字嘅紀錄 + 係咪要全部剷走晒搜尋嘅紀錄? + 頻道頁面 + 揀選一個頻道 + 排隊播 + 聲音設定 + 開選單 + 揀做播放清單縮圖 + 整咗個播放清單 + 加咗入播放清單 + LeakCanary 用唔到 + 顯示記憶體洩露 + 強制報告喺片段或活動週期棄置後送遞唔到嘅 Rx 例外狀況 + 顯示「令播放器閃退」 + 用播放器嘅時候顯示一個閃退選項 + 令個 app 閃退 + 顯示一則錯誤橫條 + 建立一則錯誤通知 + 好,包括埋睇過但未睇晒嘅影片 + 礙於 ExoPlayer 所限,快轉長短經已改為 %d 秒 + 影片雜湊通知 + 排咗去隊尾 + 揀定「開啟」動作 + 預設開啟內容嘅時候做咩好 — %s + 脫鈎 (聲音可能會失真) + 飛前一格 + Android KitKat 以及樓下唔支援「儲存空間存取框架」 + 未有收起嘅播放清單 + 照單全播 + 未有頻道訂閱 + 轉咗播放清單縮圖。 + 報告週期完結後嘅錯誤 + 每次下載都會問您要儲存去邊 + 每次下載都會問您要儲存去邊。 +\n如果您想儲存落外置 SD 卡入面,請啟用系統資料夾揀選器 (SAF) + 用系統資料夾揀選器 (SAF) + 加入去播放清單之前或之後睇過嘅影片都會剷走個囉喎。 +\n您肯定?剷走咗冇得掹個囉喎! + 「儲存空間存取框架」呢壇嘢,俾您下載落外置 SD 卡嗰度 + Android 10 打上局住要用「儲存空間存取框架」 + ExoPlayer 預設值 + 外置儲存空間用唔到 + 排隊尾輪候播 + 刪除咗搜尋紀錄 + 冇得下載落外置 SD 卡嗰度。要唔要重設下載資料夾位置? + 喺浮面開始播放 + 收起播放清單 + 唔再收起 + 係咪要刪除呢個播放清單? \ No newline at end of file From 8cd3083e52352feedfd69ef816742731e270ddf9 Mon Sep 17 00:00:00 2001 From: bw4518 Date: Mon, 14 Mar 2022 18:21:07 +0000 Subject: [PATCH 004/992] Translated using Weblate (Slovak) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-sk/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 13ecd5190..b3a7dc4a9 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -647,7 +647,7 @@ \'Storage Access Framework\' nie je podporovaný v systéme Android KitKat a ani v starších verziách Pri každom sťahovaní sa zobrazí výzva kam uložiť súbor Nie je nastavený adresár na sťahovanie, nastavte ho teraz - Označiť ako pozorované + Označiť ako videné Načítavanie podrobností o kanáli… Chyba pri zobrazení podrobností kanála Vypnuté From 8c662c9d7b611b049f47d818ed4eb2ae24488b29 Mon Sep 17 00:00:00 2001 From: NTFSynergy Date: Mon, 14 Mar 2022 15:44:50 +0000 Subject: [PATCH 005/992] Translated using Weblate (Slovak) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-sk/strings.xml | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index b3a7dc4a9..d29ae0f6c 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -118,8 +118,8 @@ Ukladať históriu pozretých videí Pokračovať v prehrávaní Pokračovať po prerušeniach (napr. telefonát) - NewPipe notifikácie - Notifikácie pre NewPipe na pozadí a v mini okne + NewPipe oznámenia + Notifikácie NewPipe prehrávača Preberanie Povolené znaky v názvoch súborov Neplatné znaky budú nahradené týmito znakmi @@ -252,7 +252,7 @@ Vymazať metadáta uložené vo vyrovnávacej pamäti Odstrániť všetky údaje webových stránok vo vyrovnávacej pamäti Vyrovnávacia pamäť metadát bola vymazaná - Automaticky zaradiť daľší stream + Automaticky zaradiť ďalší stream Končiaci (neopakujúci sa) zoznam prehrávania bude pokračovať súvisiacim streamom Ladenie Súbor @@ -284,11 +284,11 @@ \n \n1. Prejdite na túto adresu URL: %1$s \n2. Po výzve sa prihláste do svojho účtu -\n3. Kliknite na \"Zahrnuté sú všetky údaj služby YouTube\", tam kliknite na \"Zrušiť výber\", zaškrtnite \"odbery\" a potom kliknite na OK -\n4. Kliknite na \"Ďaľší krok\" a potom na \"Vytvoriť export\" +\n3. Kliknite na \"Zahrnuté sú všetky údaj služby YouTube\", kliknite na \"Zrušiť výber\", zaškrtnite \"odbery\" a kliknite na OK +\n4. Kliknite na \"Ďalší krok\" a potom na \"Vytvoriť export\" \n5. Po chvíli sa objaví tlačidlo s nápisom \"Stiahnuť\" \n6. Kliknite na IMPORT ZO SÚBORU a zvoľte stiahnutý zip súbor -\n7. Ak import zip súbory zlyhá. Stiahnutý súbor otvorte a extraktujte .csv súbor (nachádza sa v \"/Takeout/YouTube a YouTube Music/odbery/\"), tento súbor importujte do NewPipe +\n7. Ak import zip súbory zlyhá. Stiahnutý súbor otvorte a extrahujte .csv súbor (nachádza sa v \"/Takeout/YouTube a YouTube Music/odbery/\"), tento súbor importujte do NewPipe Importovať SoundCloud profil zadaním URL adresy alebo vášho ID: \n \n1. Prepnite režim na \"desktop\" (web nie je dostupný pre mobilné zariadenia) @@ -318,8 +318,8 @@ \nNewPipe v ochrane súkromia podrobne vysvetľuje, aké údaje budú odoslané a uložené pri hlásení o páde. Prečítajte si pravidlá ochrany osobných údajov Chcete zároveň importovať aj nastavenia? - V súlade s Európskym Všeobecným Nariadením o Ochrane Údajov (GDPR), chceme upriamiť vašu pozornosť na ochranu osobných údajov v NewPipe. Starostlivo si ich prečítajte. -\nMusíte ich prijať pred nahlásením chyby. + V súlade s Európskym Všeobecným Nariadením o Ochrane Údajov (GDPR), chceme upriamiť Vašu pozornosť na zmluvu o ochrane osobných údajov v NewPipe. Starostlivo si ju prečítajte. +\nMusíte ju prijať pred nahlásením chyby. Prijať Odmietnuť Bez limitu @@ -377,8 +377,8 @@ Stiahnutý súbor s týmto názvom už existuje Sťahovanie s týmto názvom už prebieha Zobraziť chybu - Adresár nemožno vytvoriť - Nemožno vytvoriť súbor + Adresár nie je možné vytvoriť + Nie je možné vytvoriť súbor Nepodarilo sa vytvoriť zabezpečené pripojenie Server sa nepodarilo nájsť Nepodarilo sa pripojiť k serveru @@ -686,7 +686,7 @@ Nové položky informačného kanála Pre túto akciu sa nenašiel vhodný správca súborov. \nNainštalujte si správcu súborov alebo skúste vypnúť \'%s\' v nastaveniach sťahovania. - Zobraziť „zlyhanie prehrávača“ + Zobraziť „zlyhať prehrávač“ LeakCanary nie je k dispozícii Pre túto akciu sa nenašiel vhodný správca súborov. \nNainštalujte správcu súborov kompatibilného s rozhraním Storage Access Framework. @@ -699,4 +699,8 @@ Zobrazí možnosť zlyhania pri používaní prehrávača Zobraziť krátke oznámenie chyby Oznámte chybu + Upraviť výšku poltónov + Krok tempa + ExoPlayer preddefinovaný + Zmeniť interval načítania (aktuálne %s). Menšia hodnota môže zvýšiť rýchlosť prvotného načítania videa. Zmena vyžaduje reštart. \ No newline at end of file From b2f22ac58411f1a365f74dfa837e8e82601cf62f Mon Sep 17 00:00:00 2001 From: Filip Marek Date: Mon, 14 Mar 2022 16:34:54 +0000 Subject: [PATCH 006/992] Translated using Weblate (Czech) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-cs/strings.xml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 2b9df57f7..a1fb98192 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -82,7 +82,7 @@ Černé Kontrolní součet Určete prosím složku pro stahování později v nastavení - Co:\\nŽádost:\\nJazyk obsahu\\nZemě obsahu:\\nJazyk aplikace:\\nSlužba:\\nČas GMT:\\nBalíček:\\nVerze:\\nVerze OS: + Co:\\nŽádost:\\nJazyk obsahu:\\nZemě obsahu:\\nJazyk aplikace:\\nSlužba:\\nČas GMT:\\nBalíček:\\nVerze:\\nVerze OS: Vše tis. Otevřít ve vyskakovacím okně @@ -312,7 +312,7 @@ Vymazat celkovou historii vyhledávání\? Historie vyhledávání smazána Jedna položka smazána. - NewPipe je copyleft libre software: Můžete jej používat, sdílet a vylepšovat dle vaší vůle. Redistribuovat a/nebo upravovat lze za podmínek GNU General Public Licence zveřejňované nadací Free Software Foundation, a to buď za podmínek licence verze 3 nebo (dle vaší volby) jakékoli pozdější verze. + NewPipe je svobodný software s copyleft licencí: Můžete jej libovolně používat, studovat, sdílet a vylepšovat. Konkrétně jej můžete šířit a/nebo upravovat za podmínek Obecné veřejné licence GNU (GNU GPL) vydané nadací Free Software Foundation, a to buď za podmínek Licence verze 3 nebo (dle vaší volby) jakékoli pozdější verze. kanály Playlisty Stopy @@ -325,7 +325,7 @@ Zrychleně vpřed během ticha Krok Reset - Abychom vyhověli Obecnému nařízení o ochraně osobních údajů (GDPR), upozorňujeme vás na zásady ochrany soukromí v NewPipe. Přečtěte si je prosím pozorně. + Abychom vyhověli Obecnému nařízení o ochraně osobních údajů (GDPR), upozorňujeme vás na zásady ochrany soukromí v NewPipe. Přečtěte si je, prosím, pozorně. \nJe potřeba je odsouhlasit, abyste nám mohli odeslat hlášení chyb. Přijmout Odmítnout @@ -367,7 +367,7 @@ Pozastaveno ve frontě post-processing - Fronta + Zařadit do fronty Akce odmítnuta systémem Stahování se nezdařilo Vytvořit jedinečný název @@ -686,7 +686,7 @@ Došlo k chybě, více v oznámení Vytvořit oznámení o chybě Kontrola aktualizací… - Ukázat \"Zřícení přehávače\" + Ukázat „Shodit přehrávač“ Hraje již v pozadí Nové položky feedů Pro tuto akci nebyl nalezen žádný vhodný správce souborů. @@ -698,5 +698,10 @@ Ukáže volbu pro zřícení během používání přehrávače Ukázat krátké oznámení o chybě Připnutý komentář - Zřícení přehrávače + Shodit přehrávač + Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače. + LeakCanary není dostupné + Upravit výšku tónů po půltónech + Krok tempa + Výchozí ExoPlayer \ No newline at end of file From 71159cf0c8ff6bb21155172939e89e9d0360fa86 Mon Sep 17 00:00:00 2001 From: zenobit Date: Mon, 14 Mar 2022 16:53:18 +0000 Subject: [PATCH 007/992] Translated using Weblate (Czech) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-cs/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index a1fb98192..6f5c26976 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -233,7 +233,7 @@ Přejmenovat Jméno Přidat do playlistu - Nastavit jako miniaturu playlistu + Nastavit jako náhled playlistu Přidat playlist do záložek Smazat záložku Smazat tento playlist\? From ac10e15c15233f81823351e6ea18f79a4f6e673c Mon Sep 17 00:00:00 2001 From: Mohammed Anas Date: Mon, 14 Mar 2022 16:24:21 +0000 Subject: [PATCH 008/992] Translated using Weblate (Arabic) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-ar/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 8f085e64a..d5408d1a9 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -242,7 +242,7 @@ تنزيل ملف البث الإشارات المرجعية استعمال التقديم السريع الغير دقيق - خاصية التقديم الغير دقيق تسمح للمشغل بالقفز خلال الفديو بشكل اسرع مع دقة قفز اقل. خاصية القفز ل5، 15 او 25 لا تعمل مع القفز الغير دقيق + خاصية التقديم الغير دقيق تسمح للمشغل بالقفز خلال الفديو بشكل أسرع مع دقة قفز أقل. خاصية القفز ل5، 15 او 25 لا تعمل مع القفز الغير دقيق تحميل الصور المصغرة تم إفراغ مساحة ذاكرة التخزين المؤقتة الخاصة بالصور الملف @@ -289,7 +289,7 @@ لا يوجد بث متاح للتنزيل تم حذف عنصر واحد. لم يتم تثبيت أي تطبيق لتشغيل هذا الملف - NewPipe هو برنامج مفتوح المصدر وبحقوق متروكة: يمكنك استخدام الكود ودراسته وتحسينه كما شئت. وعلى وجه التحديد يمكنك إعادة توزيعه / أو تعديله تحت شروط رخصة GNU العمومية والتي نشرتها مؤسسة البرمجيات الحرة، سواء الإصدار 3 من الرخصة، أو (باختيارك) أي إصدار جديد. + NewPipe هو برنامج مفتوح المصدر وبحقوق متروكة: يمكنك استخدام الكود ودراسته وتحسينه كما شئت. وعلى وجه التحديد يمكنك إعادة توزيعه / أو تعديله تحت شروط رخصة GNU العمومية والتي نشرتها مؤسسة البرمجيات الحرة، سواء الإصدار 3 من الرخصة، أو (باختيارك) أي إصدار أحدث. آخر ما تم تشغيله الأكثر تشغيلا هذا سوف يُزيل إعداداتك الحالية. @@ -300,7 +300,7 @@ نسخة احتياطية تعذر استيراد الاشتراكات لا يمكن تصدير الاشتراكات - استيراد اشتراكات YouTube من برنامج Google: + استيراد اشتراكات YouTube من Google takeout: \n \n1. انتقل إلى عنوان URL هذا: %1$s \n2. تسجيل الدخول عند سؤالك @@ -615,7 +615,7 @@ تلوين الإشعارات استخدم الصورة المصغرة لكل من خلفية شاشة القفل والإشعارات إظهار الصورة المصغرة - قم بإيقاف التشغيل لإخفاء مربعات المعلومات الوصفية بمعلومات إضافية حول منشئ، البث أو محتوى البث أو طلب البحث + قم بإيقاف التشغيل لإخفاء مربعات المعلومات الوصفية بمعلومات إضافية حول منشئ البث أو محتوى البث أو طلب البحث إظهار معلومات التعريف حديثة حساب التجزئة From 5ee961d3ebffdf957005642d48dbf8f2ab8f3076 Mon Sep 17 00:00:00 2001 From: Agnieszka C Date: Mon, 14 Mar 2022 08:29:49 +0000 Subject: [PATCH 009/992] Translated using Weblate (Polish) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-pl/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index c79653f1c..1b161a764 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -195,7 +195,7 @@ Przytrzymaj, aby zakolejkować Zacznij odtwarzać w tle Zacznij odtwarzać w trybie okienkowym - Nie znaleziono odtwarzacza strumieni (żeby odtworzyć, możesz zainstalować VLC). + Nie znaleziono odtwarzacza strumieni (żeby odtworzyć, możesz zainstalować VLC) Domyślny kraj treści Zawsze Tylko raz @@ -607,8 +607,8 @@ Awaria aplikacji Ta zawartość dostępna jest tylko dla użytkowników, którzy za nią zapłacili. Nie może ona być strumieniowana ani pobierana przez NewPipe. To wideo dostępne jest tylko dla subskrybentów usługi YouTube Music Premium. Nie może ono być strumieniowane ani pobierane przez NewPipe. - Ta zawartość jest prywatna, więc nie może być strumieniowana ani pobierana przez NewPipe. - Ta treść nie jest dostępna w Twoim kraju. + Ta zawartość jest prywatna, więc nie może być strumieniowana ani pobierana przez NewPipe + Ta treść nie jest dostępna w Twoim kraju To wideo ma ograniczenia wiekowe. \nZe względu na nowe zasady YouTube odnośnie ograniczeń wiekowych, NewPipe nie ma dostępu do żadnego z jego strumieni wideo i dlatego nie jest w stanie go odtworzyć. To jest utwór SoundCloud Go+ (przynajmniej w Twoim kraju). Nie może on być strumieniowany lub pobierany przez NewPipe. From d0ba87f7eeb8ba9bbe2f096b6422fa5d5c8d1dc5 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Tue, 15 Mar 2022 11:40:26 +0000 Subject: [PATCH 010/992] Translated using Weblate (Persian) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-fa/strings.xml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 34fda3cae..9b97546c1 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -355,8 +355,8 @@ نیوپایپ یک نرم‌افزار آزاد کپی‌لفتی است: می‌توانید به دلخواه خود استفاده، بررسی، هم‌رسانی و بهترش کنید. به طور خاص می‌توانید ذیل نگارش ۳ یا هر نگارش بالاتری از پروانهٔ جامع همگانی گنو که به دست بنیاد نرم‌افزار آزاد منتشر شده، تغییر و بازنشرش دهید. حریم شخصی شما برای پروژه نیویایپ بسیاری جدی است. در نتیجه، این برنامه هیچ اطلاعاتی را بدون رضایت شما گردآوری نمی‌کند. \nسیاست‌های حریم شخصی نیوپایپ با جزئیات توضیح می‌دهد که وقتی گزارش یک خطا را می‌فرستید، چه داده‌هایی ذخیره و فرستاده می‌شود. - به منظور هماهنگی با مقررات حفاظت داده عمومی اروپا (GDPR) در اینجا توجه شما را به سیاست حریم شخصی نیوپایپ جلب می‌کنیم. لطفا آن را به دقت مطالعه کنید. -\nبرای فرستادن گزارش مشکل، باید این سیاست را بپذیرید. + به منظور هماهنگی با مقرّرات حفاظت دادهٔ عمومی اروپا (GDPR) توجهتان را به سیاست محرمانگی نیوپایپ جلب می‌کنیم. لطفاً به دقّت مطالعه‌اش کنید. +\nبرای فرستادن گزارش مشکل باید بپذیریدش. کیفیت را در زمان استفاده از داده همراه محدود کن پرش به جلو حین سکوت بازنشانی @@ -374,11 +374,11 @@ بازنویسی یک بارگیری دیگر با همین نام در جریان است نمایش خطا - پوشه مقصد نمی‌تواند ساخته شود + شاخهٔ مقصد نمی‌تواند ساخته شود پرونده نمی‌تواند ساخته شود شکست در برقرار ارتباط امن ناتوانی در پیدا کردن کارساز - ناتوانی در اتصال به کارساز + نمی‌توان به کارساز وصل شد کارساز داده‌ای نمی‌فرستد کارساز، بارگیری‌های چندرشته‌ای را قبول نمی‌کند، تلاش مجدد با ‎@string/msg_threads = 1 توقف @@ -688,4 +688,8 @@ نظر سنجاق شده در حال پخش در پس‌زمینه لیک‌کاناری موجود نیست + گام سرعت + تنظیم زیر و بم با شبه‌تن‌ها + تغییر اندازهٔ بازهٔ بار (هم‌اکنون %s). مقداری پایین‌تر، می‌تواند بار کردن نخستین ویدیو را سرعت بخشد. تغییرها نیاز به یک آغاز دوبارهٔ پخش‌کننده دارند. + پیش‌گزیدهٔ اگزوپلیر \ No newline at end of file From 7898c33819e593cf34dfec2abfcd0476098ce2ef Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Mon, 14 Mar 2022 15:18:01 +0000 Subject: [PATCH 011/992] Translated using Weblate (Hebrew) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-he/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 48a79282e..c65da25ac 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -715,4 +715,5 @@ התאמת גובה הצליל לפי חצאי טונים מוזיקליים צעד מקצב ברירת מחדל של ExoPlayer + שינוי גודל מרווח הטעינה (כרגע %s). ערך נמוך יותר עשוי להאיץ את טעינת הווידאו הראשונית. שינויים דורשים את הפעלת הנגן מחדש. \ No newline at end of file From 4ea0d05c17a3a5310f7d869318316303254a4734 Mon Sep 17 00:00:00 2001 From: subba raidu Date: Fri, 18 Mar 2022 06:53:33 +0000 Subject: [PATCH 012/992] Translated using Weblate (Telugu) Currently translated at 65.1% (402 of 617 strings) --- app/src/main/res/values-te/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index b5d5ef0b7..df94babea 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -420,4 +420,11 @@ పూరించండి పరివీక్షణ తేలియాడే విధంగా మార్చు + తదుపరి స్ట్రీమ్‌ను స్వీయ-ఎన్క్యూ + లోడ్ విరామం పరిమాణాన్ని మార్చండి (ప్రస్తుతం %s). తక్కువ విలువ ప్రారంభ వీడియో లోడింగ్‌ని వేగవంతం చేయవచ్చు. మార్పులకు ప్లేయర్ రీస్టార్ట్ అవసరం. + LeakCanary అందుబాటులో లేదు + ప్లేయర్ శీర్షిక వచన స్థాయి మరియు నేపథ్య శైలులను సవరించండి. అమలులోకి రావడానికి యాప్ రీస్టార్ట్ అవసరం + స్వయంచాలకంగా రూపొందించబడింది (ఎక్కించినవారు కనబడుటలేదు) + స్వయంచాలకంగా రూపొందించబడింది + శీర్షికలు \ No newline at end of file From 8cc29241e20df01af886cccd56595d549bb10add Mon Sep 17 00:00:00 2001 From: Marco Santos Date: Wed, 16 Mar 2022 07:46:15 +0000 Subject: [PATCH 013/992] Translated using Weblate (Filipino) Currently translated at 18.8% (116 of 617 strings) --- app/src/main/res/values-fil/strings.xml | 130 ++++++++++++++++++------ 1 file changed, 101 insertions(+), 29 deletions(-) diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index 6a0a123e7..8386c895f 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -1,46 +1,118 @@ - Pindutin ang \'hanap\' upang magsimula - Nai-publish noong %1$s - Walang nakitang stream player. Mag-install ng VLC\? - Walang nahanap na stream player (puwede mag-install ng VLC upang i-play). + Pindutin ang magnifying glass para makapagsimula. + Inilathala noong %1$s + Walang nakitang stream player. I-install ang VLC\? + Walang nakitang stream player (pwede mong i-install ang VLC para ma-play ito). I-install Kanselahin - Buksan sa Browser - Buksan sa Popup Mode + Buksan sa browser + Buksan sa popup mode Ibahagi I-download - Mag-download ng stream file - Hanap - Setting - Ibig mo sabihin: %1$s\? + I-download ang stream file + Maghanap + Pagsasaayos + Ibig mo bang sabihin \"%1$s\"\? Ibahagi sa - Gumamit ng panlabas na video player - Tanggal ang audio sa ilang resolusyon - Gumamit ng panlabas na audio player + Gumamit ng ibang video player + Natatanggal ang tunog sa ilang mga resolusyon + Gumamit ng ibang audio player Mag-subscribe Naka-subscribe Mag-unsubscribe - Hindi naka-subscribe sa channel - Hindi mabago ang subscription - Hindi ma-update ang subscription - Ipakita ang impo + Na-unsubscribe na sa channel + Di mabago ang subscription + Di ma-update ang subscription + Ipakita ang info Mga subscription - Naka-bookmark na Playlist - Pumili nang Tab + Naka-bookmark na playlist + Pumili ng Tab Anong Bago - Background + Likuran Popup Idagdag sa - Folder ng video download - Pumili ng download folder para sa video - Nakaimbak dito ang na-download na audio file - Pumili ng download folder ng audio file + Download folder ng mga video + Pumili ng download folder para sa mga video file + Ilalagay rito ang mga na-download na audio file + Pumili ng download folder para sa mga audio file Maliwanag - Buksan gamit Kodi + I-play gamit Kodi Madilim - Dito nakaimbak na-download na video - Audio download folder - Pinapakitang resulta para sa: %s - Buksan gámit ang + Ilalagay rito ang mga na-download na video file + Download folder ng mga audio + Ipinapakita ang mga resulta para sa: %s + Buksan gamit ng + Pang-apat na action button + Ipakita ang \"I-play gamit Kodi\" + Pangalawang action button + Pangatlong action button + Pinapayagan ng di-saktong seek ang player na mag-seek sa mga posisyon nang mabilis ngunit na may pinababang kasaktuhan. Di ito gagana sa pag-seek nang 5, 15, o 25 segundo. + Baguhin ang laki ng pagitan na ilo-load (kasalukuyang %s). Maaaring mapabilis ang unang pag-load sa video kung mababa ito. Kailangang i-restart ang player para gumana ang pagbabago. + Patayin para mapigilan ang pag-load sa mga thumbnail, para makatipid ng data at paggamit sa memory. Lilinisin ang parehong image cache na nasa memory at nasa disk + Piliin ang mga mungkahing ipapakita habang naghahanap + Patayin para itago ang paglalarawan ng video at karagdagang impormasyon + I-edit ang bawat action sa abiso sa baba sa pamamagitan ng pagpindot sa mga ito. Pumili ng hanggang tatlong ipapakita sa siksik na abiso gamit ang mga checkbox sa kanan + Pag-buffer + Wala + Tema + Lokal na mungkahi + Remote na mungkahi + Markahang napanood + Kusang ipila + Ulitin + Halo-halo + Hayaan ang Android na baguhin ang kulay ng abiso depende sa pangunahing kulay ng thumbnail (paalala, hindi ito available sa lahat ng mga device) + Audio + Sige + Panimulang linaw + Panlimang action button + Kulayan ang abiso + Kumpirmahin muna bago linisin ang pila + Maaaring mapalitan ang pila mo kung magpapalit ka ng player + Papalitan ang aktibong pila sa player + I-load ang mga thumbnail + Ipakita ang paglalarawan + Ipakita ang meta info + Patayin para itago ang mga meta infobox na may karagdagang impormasyon tungkol sa creator ng stream, laman nito o ng hinanap + Tanggalin ang lahat ng mga naka-cache na data ng webpage + Nalinis na ang cache ng metadata + Kusang ipila ang susunod na stream + Ipagpatuloy na tapusin (di umuulit) ang pila sa pamamagitan ng pagdagdag ng isang katulad na stream + Gumamit ng gesture para kontrolin ang volume ng player + Mga mungkahi sa paghahanap + Itago ang mga hinanap nang lokal + Kasaysayan ng napanood + Ipagpatuloy ang pag-play + Pumunta sa posisyon ng huling pag-play + Ipakita ang mga \'Susunod\' at \'Katulad\' na video + Linisin ang data + Ipakita ang pananda ng posisyon ng pag-play sa mga listahan + Mga posisyon sa listahan + Kasaysayan ng mga hinanap + Gumamit ng gesture para kontrolin ang liwanag ng player + Kontrolin ang liwanag gamit gesture + Kontrolin ang volume gamit gesture + Linisin ang naka-cache na metadata + Nalinis na ang image cache + Patayin para itago ang mga komento + Ipakita ang mga komento + Maitim + Magpapakita ng option na i-play ang video sa Kodi media center + Ipakita ang mga mas mataas na linaw + I-crash ang player + Tandaan ang mga property ng popup + Panimulang linaw ng popup + Mape-play lang ng ilang device ang mga video na 2K/4K + I-install ang nawawalang Kore app\? + I-scale ang thumbnail sa 1:1 aspect ratio + I-scale ang thumbnail ng video sa abiso mula 16:9 papuntang 1:1 aspect ratio (pwedeng magkaroon ng distortion) + Unang action button + Hanggang tatlo lang ang pwedeng maipakita sa siksik na abiso! + Panimulang audio format + Panimulang video format + Madilim na Tema + Tandaan ang huling laki at posisyon ng popup + Gamitin ang mabilis ngunit di-saktong seek + Haba ng fast forward/-rewind seek \ No newline at end of file From f5e253456ce33c695fe07fa239dbaeadfb4a97a5 Mon Sep 17 00:00:00 2001 From: AHOHNMYC Date: Wed, 16 Mar 2022 01:45:13 +0000 Subject: [PATCH 014/992] Translated using Weblate (Russian) Currently translated at 16.9% (11 of 65 strings) Translation: NewPipe/Metadata Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/ --- fastlane/metadata/android/ru/changelogs/65.txt | 1 - fastlane/metadata/android/ru/changelogs/66.txt | 1 - fastlane/metadata/android/ru/changelogs/68.txt | 1 - fastlane/metadata/android/ru/changelogs/69.txt | 1 - fastlane/metadata/android/ru/changelogs/70.txt | 1 - fastlane/metadata/android/ru/changelogs/71.txt | 1 - fastlane/metadata/android/ru/changelogs/730.txt | 1 - fastlane/metadata/android/ru/changelogs/740.txt | 1 - fastlane/metadata/android/ru/changelogs/750.txt | 1 - fastlane/metadata/android/ru/changelogs/760.txt | 1 - fastlane/metadata/android/ru/changelogs/770.txt | 1 - fastlane/metadata/android/ru/changelogs/780.txt | 1 - fastlane/metadata/android/ru/changelogs/790.txt | 1 - fastlane/metadata/android/ru/changelogs/800.txt | 1 - fastlane/metadata/android/ru/changelogs/810.txt | 1 - fastlane/metadata/android/ru/changelogs/820.txt | 1 - fastlane/metadata/android/ru/changelogs/830.txt | 1 - fastlane/metadata/android/ru/changelogs/840.txt | 1 - fastlane/metadata/android/ru/changelogs/850.txt | 1 - fastlane/metadata/android/ru/changelogs/860.txt | 1 - fastlane/metadata/android/ru/changelogs/870.txt | 1 - fastlane/metadata/android/ru/changelogs/900.txt | 1 - fastlane/metadata/android/ru/changelogs/910.txt | 1 - fastlane/metadata/android/ru/changelogs/920.txt | 1 - fastlane/metadata/android/ru/changelogs/930.txt | 1 - fastlane/metadata/android/ru/changelogs/940.txt | 1 - fastlane/metadata/android/ru/changelogs/950.txt | 5 ++++- fastlane/metadata/android/ru/changelogs/951.txt | 1 - fastlane/metadata/android/ru/changelogs/952.txt | 8 +++++++- fastlane/metadata/android/ru/changelogs/953.txt | 2 +- fastlane/metadata/android/ru/changelogs/954.txt | 1 - fastlane/metadata/android/ru/changelogs/957.txt | 1 - 32 files changed, 12 insertions(+), 32 deletions(-) delete mode 100644 fastlane/metadata/android/ru/changelogs/65.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/66.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/68.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/69.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/70.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/71.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/730.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/740.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/750.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/760.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/770.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/780.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/790.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/800.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/810.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/820.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/830.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/840.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/850.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/860.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/870.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/900.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/910.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/920.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/930.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/940.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/951.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/954.txt delete mode 100644 fastlane/metadata/android/ru/changelogs/957.txt diff --git a/fastlane/metadata/android/ru/changelogs/65.txt b/fastlane/metadata/android/ru/changelogs/65.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/65.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/66.txt b/fastlane/metadata/android/ru/changelogs/66.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/66.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/68.txt b/fastlane/metadata/android/ru/changelogs/68.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/68.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/69.txt b/fastlane/metadata/android/ru/changelogs/69.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/69.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/70.txt b/fastlane/metadata/android/ru/changelogs/70.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/70.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/71.txt b/fastlane/metadata/android/ru/changelogs/71.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/71.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/730.txt b/fastlane/metadata/android/ru/changelogs/730.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/730.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/740.txt b/fastlane/metadata/android/ru/changelogs/740.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/740.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/750.txt b/fastlane/metadata/android/ru/changelogs/750.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/750.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/760.txt b/fastlane/metadata/android/ru/changelogs/760.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/760.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/770.txt b/fastlane/metadata/android/ru/changelogs/770.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/770.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/780.txt b/fastlane/metadata/android/ru/changelogs/780.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/780.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/790.txt b/fastlane/metadata/android/ru/changelogs/790.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/790.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/800.txt b/fastlane/metadata/android/ru/changelogs/800.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/800.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/810.txt b/fastlane/metadata/android/ru/changelogs/810.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/810.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/820.txt b/fastlane/metadata/android/ru/changelogs/820.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/820.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/830.txt b/fastlane/metadata/android/ru/changelogs/830.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/830.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/840.txt b/fastlane/metadata/android/ru/changelogs/840.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/840.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/850.txt b/fastlane/metadata/android/ru/changelogs/850.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/850.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/860.txt b/fastlane/metadata/android/ru/changelogs/860.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/860.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/870.txt b/fastlane/metadata/android/ru/changelogs/870.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/870.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/900.txt b/fastlane/metadata/android/ru/changelogs/900.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/900.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/910.txt b/fastlane/metadata/android/ru/changelogs/910.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/910.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/920.txt b/fastlane/metadata/android/ru/changelogs/920.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/920.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/930.txt b/fastlane/metadata/android/ru/changelogs/930.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/930.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/940.txt b/fastlane/metadata/android/ru/changelogs/940.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/940.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/950.txt b/fastlane/metadata/android/ru/changelogs/950.txt index 8d1c8b69c..234065709 100644 --- a/fastlane/metadata/android/ru/changelogs/950.txt +++ b/fastlane/metadata/android/ru/changelogs/950.txt @@ -1 +1,4 @@ - +В этой версии только три малых исправления: +• Исправлен доступ к хранилищу на Android 10+ +• Исправлено открытие киоска +• Исправлено определение длительности для долгих видео diff --git a/fastlane/metadata/android/ru/changelogs/951.txt b/fastlane/metadata/android/ru/changelogs/951.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/951.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/952.txt b/fastlane/metadata/android/ru/changelogs/952.txt index 8d1c8b69c..4c6e1c8b5 100644 --- a/fastlane/metadata/android/ru/changelogs/952.txt +++ b/fastlane/metadata/android/ru/changelogs/952.txt @@ -1 +1,7 @@ - +Улучшения +• Автовоспроизведение доступно для всех сервисов (а не только для YouTube) + +Исправления +• Исправлено перемещение по трансляции на YouTube +• Доступны видео YouTube с ограничением по возрасту +• [Android TV] Исправлен долго не скрывающийся интерфейс diff --git a/fastlane/metadata/android/ru/changelogs/953.txt b/fastlane/metadata/android/ru/changelogs/953.txt index 8d1c8b69c..8e74aa7f8 100644 --- a/fastlane/metadata/android/ru/changelogs/953.txt +++ b/fastlane/metadata/android/ru/changelogs/953.txt @@ -1 +1 @@ - +Починка функции извлечения описания из YouTube. diff --git a/fastlane/metadata/android/ru/changelogs/954.txt b/fastlane/metadata/android/ru/changelogs/954.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/954.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/fastlane/metadata/android/ru/changelogs/957.txt b/fastlane/metadata/android/ru/changelogs/957.txt deleted file mode 100644 index 8d1c8b69c..000000000 --- a/fastlane/metadata/android/ru/changelogs/957.txt +++ /dev/null @@ -1 +0,0 @@ - From 8ca701b8825f3fdacc68390a6a73aa3e79b596ad Mon Sep 17 00:00:00 2001 From: Igor Sorocean Date: Wed, 16 Mar 2022 09:06:28 +0000 Subject: [PATCH 015/992] Translated using Weblate (Romanian) Currently translated at 9.2% (6 of 65 strings) Translation: NewPipe/Metadata Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ro/ --- fastlane/metadata/android/ro/changelogs/64.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 fastlane/metadata/android/ro/changelogs/64.txt diff --git a/fastlane/metadata/android/ro/changelogs/64.txt b/fastlane/metadata/android/ro/changelogs/64.txt new file mode 100644 index 000000000..ca2d405b5 --- /dev/null +++ b/fastlane/metadata/android/ro/changelogs/64.txt @@ -0,0 +1,8 @@ +### Îmbunătățiri +- A fost adăugată posibilitatea de a limita calitatea video în cazul în care se utilizează date mobile. #1339 +- Memorizare luminozitate pentru sesiune #1442 +- Îmbunătățirea performanțelor de descărcare pentru procesoarele mai slabe #1431 +- Adăugare suport (în lucru) pentru sesiunea media #1433 + +### Fix +- Corectarea blocării la deschiderea descărcărilor (corecția este acum disponibilă pentru versiunile de lansare) #1441 From cbc718437b4f1448ef4e32b60078af3f930e3433 Mon Sep 17 00:00:00 2001 From: Filip Marek Date: Mon, 14 Mar 2022 17:03:54 +0000 Subject: [PATCH 016/992] Translated using Weblate (Czech) Currently translated at 4.6% (3 of 65 strings) Translation: NewPipe/Metadata Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/ --- fastlane/metadata/android/cs/full_description.txt | 2 +- fastlane/metadata/android/cs/short_description.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/metadata/android/cs/full_description.txt b/fastlane/metadata/android/cs/full_description.txt index 0cd151794..ac0c27080 100644 --- a/fastlane/metadata/android/cs/full_description.txt +++ b/fastlane/metadata/android/cs/full_description.txt @@ -1 +1 @@ -NewPipe nepoužívá žádné knihovny Google Framework nebo YouTube API. Analyzuje pouze YouTube website, aby získala ty informace, které potřebuje. Proto lze tuto aplikaci použít na zařízeních bez softwaru Google Services. Stejně tak nepotřebujete k používání NewPipe žádný YouTube účet a aplikace je FLOSS. +NewPipe nepoužívá žádné knihovny Google Framework nebo YouTube API. Aplikace pouze analyzuje webovou stránku YouTube, aby získala potřebné informace. Proto lze tuto aplikaci použít i na zařízeních bez nainstalovaných Služeb Google. K používání NewPipe navíc není potřeba žádný YouTube účet, je to snadné. diff --git a/fastlane/metadata/android/cs/short_description.txt b/fastlane/metadata/android/cs/short_description.txt index 5bb79c281..36dc2a0e6 100644 --- a/fastlane/metadata/android/cs/short_description.txt +++ b/fastlane/metadata/android/cs/short_description.txt @@ -1 +1 @@ -Svobodná, lehká YouTube aplikace for Android. +Svobodný a nenáročný YouTube přehrávač for Android. From 5522a7a2e5f55f857387d23b2b6d6ec3ee3f23a0 Mon Sep 17 00:00:00 2001 From: NTFSynergy Date: Mon, 14 Mar 2022 15:53:04 +0000 Subject: [PATCH 017/992] Translated using Weblate (Slovak) Currently translated at 6.1% (4 of 65 strings) Translation: NewPipe/Metadata Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/ --- fastlane/metadata/android/sk/changelogs/63.txt | 8 ++++++++ fastlane/metadata/android/sk/short_description.txt | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/sk/changelogs/63.txt diff --git a/fastlane/metadata/android/sk/changelogs/63.txt b/fastlane/metadata/android/sk/changelogs/63.txt new file mode 100644 index 000000000..e7c9611ca --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/63.txt @@ -0,0 +1,8 @@ +### Vylepšenia +- Nastavenia importu/exportu #1333 +- Zníženie overdrawu (zlepšenie výkonu) #1371 +- Malé vylepšenia kódu #1375 +- Všetko ohľadom GDPR #1420 + +### Opravené +- Sťahovač: Oprava pádu počas načítania nedokončených súborov s príponou .giga #1407 diff --git a/fastlane/metadata/android/sk/short_description.txt b/fastlane/metadata/android/sk/short_description.txt index 61b35149a..1fe84348f 100644 --- a/fastlane/metadata/android/sk/short_description.txt +++ b/fastlane/metadata/android/sk/short_description.txt @@ -1 +1 @@ -Jednoduchá a bezplatná YouTube aplikácia pre Android. +Jednoduchý a bezplatný YouTube prehrávač pre Android. From e5ee4059719198309f6864b4bf1f7ac3583d4430 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 16 Mar 2022 00:21:46 +0000 Subject: [PATCH 018/992] Translated using Weblate (Chinese (Traditional)) Currently translated at 60.0% (39 of 65 strings) Translation: NewPipe/Metadata Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/ --- fastlane/metadata/android/zh-Hant/changelogs/982.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/982.txt diff --git a/fastlane/metadata/android/zh-Hant/changelogs/982.txt b/fastlane/metadata/android/zh-Hant/changelogs/982.txt new file mode 100644 index 000000000..dafbbc272 --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/982.txt @@ -0,0 +1 @@ +修復 YouTube 不會播放任何影片。 From 5343781e142a0087bfdf8b1a2a42a790353955d5 Mon Sep 17 00:00:00 2001 From: cedspam Date: Tue, 22 Mar 2022 08:36:30 +0000 Subject: [PATCH 019/992] Translated using Weblate (French) Currently translated at 99.6% (615 of 617 strings) --- app/src/main/res/values-fr/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 50fe7b687..b7ec76ab1 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -317,7 +317,7 @@ Refuser Le projet NewPipe prend votre vie privée très à cœur. Par conséquent, l’application n’envoie aucune donnée sans votre consentement. \nLa politique de confidentialité de NewPipe explique en détail quelles données sont envoyées et stockées lorsque vous envoyez un rapport de plantage. - Afin de se conformer au Règlement Général sur la Protection des Données (RGPD), nous attirons votre attention sur la politique de confidentialité de NewPipe. Veuillez la lire attentivement. + Afin de se conformer au Règlement Général sur la Protection des Données (RGPD), nous attirons ici votre attention sur la politique de confidentialité de NewPipe. Veuillez la lire attentivement. \nVous devez l’accepter pour nous envoyer le rapport de bogue. Aucune limite Limiter la définition lors de l’utilisation des données mobiles @@ -690,4 +690,5 @@ Commentaire épinglé Une lecture est déjà en arrière-plan LeakCanary n\'est pas disponible + Modifie la taille de l\'intervalle de chargement (actuellement %s). Une valeur plus faible peut accélérer le chargement initial des vidéos . \ No newline at end of file From 335cc234c822b5c45d9577de406a1c0bf371d8c8 Mon Sep 17 00:00:00 2001 From: translator Date: Thu, 24 Mar 2022 18:13:43 +0000 Subject: [PATCH 020/992] Translated using Weblate (French) Currently translated at 99.6% (615 of 617 strings) --- app/src/main/res/values-fr/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b7ec76ab1..5eb543ccc 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -691,4 +691,7 @@ Une lecture est déjà en arrière-plan LeakCanary n\'est pas disponible Modifie la taille de l\'intervalle de chargement (actuellement %s). Une valeur plus faible peut accélérer le chargement initial des vidéos . + Règler la hauteur par demi-tons musicaux + Pas du tempo + ExoPlayer par défaut \ No newline at end of file From 75f601c154294537ab67b2eb9fbfa4efb59d7779 Mon Sep 17 00:00:00 2001 From: pjammo Date: Sun, 20 Mar 2022 16:51:35 +0000 Subject: [PATCH 021/992] Translated using Weblate (Italian) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-it/strings.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 77b4428b0..0faacfa3f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -689,6 +689,7 @@ Già in riproduzione in sottofondo LeakCanary non è disponibile Regola il tono secondo i semitoni musicali - ExoPlayer predefinito - Cambia la dimensione dell\'intervallo da caricare (attualmente %s). Un basso valore potrebbe velocizzare il caricamento iniziale del video. Il cambiamento richiede un riavvio del player. + Predefinito ExoPlayer + Cambia la dimensione dell\'intervallo da caricare (attualmente %s). Un valore basso può velocizzare il caricamento iniziale del video. La modifica richiede il riavvio del lettore. + Passo tempo \ No newline at end of file From 70d4369d81745593e3aa4ad9f240c094cf9a9c21 Mon Sep 17 00:00:00 2001 From: Linerly Date: Sun, 20 Mar 2022 07:44:06 +0000 Subject: [PATCH 022/992] Translated using Weblate (Indonesian) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-in/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 8c01bbbcb..69011dec3 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -676,4 +676,8 @@ Komentar dipin Sudah diputar di latar belakang LeakCanary tidak tersedia + Langkah tempo + Default ExoPlayer + Atur nada berdasarkan semitone musik + Ubah ukuran interval pemuatan (saat ini %s). Sebuah nilai yang rendah mungkin dapat membuat pemuatan video awal lebih cepat. Membutuhkan sebuah pemulaian ulang pada pemain. \ No newline at end of file From 32b294f1f3dd7a909424f43645e0deccb26a819f Mon Sep 17 00:00:00 2001 From: zica <9918800@gmail.com> Date: Tue, 22 Mar 2022 07:45:38 +0000 Subject: [PATCH 023/992] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 78 +++++++++++----------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b4c8cc8e8..0f74f5bde 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,7 +1,7 @@ %1$s 發布 - 找不到串流播放器。安裝 VLC 嗎? + 找不到串流播放器。要安裝 VLC 嗎? 安裝 取消 以瀏覽器開啟 @@ -10,17 +10,17 @@ 搜尋 設定 您是不是要找「%1$s」? - 分享影片 + 以……分享 使用外部影片播放器 使用外部音訊播放器 影片下載資料夾 - 已下載的影片檔案會儲存在這裡 + 下載好的影片檔案會儲存在這裡 選擇影片檔的下載資料夾 預設解析度 用 Kodi 播放 顯示用 Kodi 媒體中心播放影片的選項 聲音 - 安裝遺失的 Kore 應用程式嗎? + 未找到 Kore app,是否安裝? 顯示「用 Kodi 播放」的選項 預設音訊格式 主題 @@ -28,7 +28,7 @@ 明亮 下載 顯示「下一部」與「相關」的影片 - 不支援此網址 + 不支援此 URL 預設內容語言 影片與音訊 外觀 @@ -39,7 +39,7 @@ 喜歡 不喜歡 音訊下載資料夾 - 已下載的音訊檔案會儲存在這裡 + 下載好的音訊檔案會儲存在這裡 選擇音訊檔的下載資料夾 輕觸放大鏡以開始使用。 以懸浮視窗開啟 @@ -146,7 +146,7 @@ © %1$s 由 %2$s 使用 %3$s 授權條款發佈 關於 授權條款 - Android 上自由且輕巧的 YouTube 串流播放器。 + Android 上自由且輕巧的串流播放器。 在 GitHub 上檢視 NewPipe 使用的授權條款 不管你有什麼點子——翻譯、設計、程式碼整理,或者程式碼撰寫——我們永遠歡迎你來幫忙。完成的越多,NewPipe 也會更好! @@ -155,7 +155,7 @@ 歷史紀錄 歷史紀錄 確定要刪除此項搜尋紀錄嗎? - 找不到串流播放器(您可以安裝 VLC 播放)。 + 找不到串流播放器(您可以安裝 VLC 來播放)。 顯示「長按以新增」提示 預設內容國家 全部播放 @@ -177,7 +177,7 @@ 匯入資料庫 匯出資料庫 覆蓋您目前的歷史記錄、訂閱與(可選的)設定 - 匯出歷史記錄、訂閱、播放清單與設定 + 匯出歷史記錄、訂閱內容、播放清單、設定 回饋 如欲了解更多有關 NewPipe 的資訊和新聞,請造訪我們的網站。 首頁內容 @@ -192,7 +192,7 @@ 無有效的 ZIP 檔案 警告:無法匯入所有檔案。 這將覆蓋您目前的設定。 - 動向 + 熱門 前 50 最新與熱門 移除 @@ -211,7 +211,7 @@ 正在載入要求的內容 下載串流檔案 顯示資訊 - 書籤已加入播放清單 + 已加入書籤的播放清單 新增至 拖曳以重新排序 建立 @@ -255,8 +255,8 @@ 正在匯入… 正在匯出… 匯入檔案 - 無法匯入訂閱 - 無法匯出訂閱 + 無法匯入訂閱內容 + 無法匯出訂閱內容 之前的匯出 檔案不存在或讀取或寫入權限不足 從 Google Takeout 匯入您的 YouTube 訂閱: @@ -280,10 +280,10 @@ \n4. 複製您被重新導向到的個人設定檔網址。 載入縮圖 關閉以防止載入縮圖,減少數據和儲存空間的用量。改變時將清除記憶體和磁碟上的縮圖快取 - 已清除圖片快取 - 清除快取中介資料 - 移除所有網頁的快取資料 - 已清除中介資料快取 + 已抹除圖片快取 + 抹除已快取的中介資料 + 移除所有已快取的網頁資料 + 已抹除中介資料快取 播放速度控制 節奏 音高 @@ -337,7 +337,7 @@ 已刪除檔案 應用程式更新通知 新 NewPipe 版本通知 - 外部儲存不可用 + 無法使用外部儲存空間 無法下載到外部 SD 卡。重設下載資料夾位置? 無法讀取已儲存的分頁,因此使用預設分頁 恢復預設值 @@ -394,7 +394,7 @@ 在清單中顯示播放位置指示器 清除資料 播放位置已刪除 - 檔案已移動或已刪除 + 檔案已遭移動或刪除 與此同名的檔案已存在 無法覆寫檔案 已有擱置中的下載與此同名 @@ -410,7 +410,7 @@ 每次下載都會詢問您要下載到哪裡。 \n如果您想要下載到外部的 SD 記憶卡的話,請啟用系統資料夾挑選器 (SAF) 使用系統資料夾挑選器 (SAF) - 「儲存存取框架」讓您可以下載到外部的 SD 記憶卡 + 「儲存空間存取框架」讓您可以下載到外部的 SD 記憶卡 刪除播放位置 刪除所有播放位置 刪除所有播放位置? @@ -425,7 +425,7 @@ 沒有人正在聽 - %s 個聽眾 + %s 位聽眾 語言將會在重新啟動應用程式後變更 快轉/快退搜尋持續時間 @@ -505,7 +505,7 @@ \n希望它會在未來的版本中支援。 ∞ 部影片 超過 100 部影片 - 藝術家 + 演出者 專輯 歌曲 此影片設有年齡限制。 @@ -518,7 +518,7 @@ 移除已觀看 來自服務的原始文字將在串流項目中可見 在項目上顯示原始時間 - 開啟 YouTube 的「受限模式」 + 開啟 YouTube 的「嚴格篩選模式」 由 %s 由 %s 建立 頻道大頭貼縮圖 @@ -529,7 +529,7 @@ 請檢查是否已有與您的應用程式當機相關的討論議題。當建立重複的工單時,您就是在耗費我們本來可以用來修復臭蟲的時間。 在 GitHub 上回報 複製格式化過的報告 - 正在顯示結果:%s + 目前顯示 %s 的結果 從不 僅在 Wi-Fi 上 自動開始播放 — %s @@ -540,10 +540,10 @@ 從一個播放器切換到另一個可能會取代您的佇列 清除佇列前要求確認 通知 - 沒有東西 + 正在緩衝 隨機播放 - 重複 + 循環播放 您可以選取最多三個動作來顯示在簡潔通知中! 透過點擊下方的每一個通知來編輯它。使用右側的勾選框,最多可以選取三個在簡潔通知中顯示 第五動作按鈕 @@ -551,7 +551,7 @@ 第三動作按鈕 第二動作按鈕 第一動作按鈕 - 將通知中顯示的影片縮圖從 16:9 縮放到 1:1(可能會導致失真) + 將通知中顯示的影片縮圖從 16:9 縮放到 1:1(可能會導致變形) 把縮圖縮放到 1:1 的長寬比 顯示記憶體洩漏 已加入佇列 @@ -559,7 +559,7 @@ 清除 NewPipe 在您解決 reCAPTCHA 時儲存的 cookie reCAPTCHA cookies 已被清除 清除 reCAPTCHA cookies - YouTube 提供了「受限模式」,可以隱藏潛在的成人內容 + YouTube 有提供「嚴格篩選模式」,可以隱藏潛在的成人內容 顯示可能不適於兒童的內容(因為其有年齡限制,如18歲以上等) 讓 Android 根據縮圖中的主要色彩來自訂通知的顏色(請注意,此功能不是在所有裝置上都能正常運作) 彩色通知 @@ -572,12 +572,12 @@ 關閉以隱藏帶有關於串流建立者、串流內容或搜尋請求等額外資料的詮釋資料框 顯示詮釋資訊 章節 - 描述 - 相關的串流 + 說明 + 相關項目 留言 - 關閉以隱藏影片描述與其他資訊 - 顯示描述 - 開啟以 + 關閉以隱藏影片說明及其他附帶資訊 + 顯示說明 + 以……開啟 您裝置上沒有應用程式可以打開這個 應用程式當機 此內容僅對已付費的內容可用,因此無法由 NewPipe 串流或下載。 @@ -623,8 +623,8 @@ \n您想要取消訂閱此頻道嗎? 無法載入「%s」。 載入 feed 時發生錯誤 - 從 Android 10 開始僅支援「儲存存取框架」 - Android KitKat 或更舊的版本不支援「儲存存取框架」 + 從 Android 10 開始僅支援「儲存空間存取框架」 + Android KitKat 或更舊的版本不支援「儲存空間存取框架」 每次下載都會詢問您要下載到哪裡 尚未設定下載資料夾,立刻選擇預設的下載資料夾 關閉 @@ -637,7 +637,7 @@ 高品質(較大) 拖動列縮圖預覽 被創作者加心號 - 標記為已觀看 + 標記為已看過 正在載入頻道詳細資訊…… 顯示頻道詳細資訊時發生錯誤 在圖片頂部顯示畢卡索彩色絲帶,指示其來源:紅色代表網路、藍色代表磁碟、綠色代表記憶體 @@ -650,7 +650,7 @@ %s 下載已完成 - 滑動項目以刪除它們 + 滑動項目即可移除 如果自動旋轉被鎖定,請不要在迷你播放器中啟動影片,而是直接切換到全螢幕模式。您仍然可以透過結束全螢幕存取迷你播放器 以全螢幕開始主播放器 已將下一個加入佇列 @@ -660,7 +660,7 @@ 手動檢查新版本 正在檢查更新…… 新 feed 項目 - 播放器當機 + 使播放器當機 顯示「播放器當機」 使用播放器時顯示當機選項 錯誤回報通知 @@ -673,7 +673,7 @@ \n請安裝檔案管理程式或在下載設定嘗試停用「%s」。 NewPipe 遇到錯誤,點擊以回報 發生錯誤,請檢視通知 - 釘選留言 + 釘選的留言 已經在背景播放 LeakCanary 無法使用 步進時間 From e72bb87cc1cbc3b5ded612ee6844011806fac281 Mon Sep 17 00:00:00 2001 From: TXRdev Archive Date: Sun, 27 Mar 2022 14:13:15 +0000 Subject: [PATCH 024/992] Translated using Weblate (Vietnamese) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-vi/strings.xml | 75 ++++++++++++++------------ 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 0d528a3b9..66329b55b 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1,6 +1,6 @@ - Nhấn vào kính lúp để bắt đầu. + Nhấn vào nút tìm kiếm để bắt đầu. Đăng vào %1$s Không tìm thấy trình phát. Cài đặt VLC\? Cài đặt @@ -44,13 +44,13 @@ Đề xuất tìm kiếm Chọn các đề xuất để hiển thị khi tìm kiếm Tải về - Hiển thị video \"Tiếp theo\" và \"Tương tự\" + Hiện video \"Tiếp theo\" và \"Tương tự\" URL không được hỗ trợ Hiển thị Phát ở chế độ nền Phát ở chế độ popup Nội dung - Cho phép và hiển thị nội dung có giới hạn độ tuổi + Hiển thị nội dung bị giới hạn độ tuổi Trực tiếp Tải xuống Tải xuống @@ -69,7 +69,7 @@ Ứng dụng / Giao diện người dùng bị lỗi Xin lỗi, điều đó không nên xảy ra. Báo lỗi qua email - Xin lỗi, có gì đó đã xảy ra. + Xin lỗi, đã xảy ra sự cố. Báo cáo Thông tin: Chuyện gì đã xảy ra: @@ -79,7 +79,7 @@ Xem video, thời lượng: Hình thu nhỏ của avatar người tải lên Lượt thích - Dislike + Lượt không thích Video Âm thanh Thử lại @@ -174,14 +174,14 @@ Xóa lịch sử xem Xóa lịch sử các luồng đã phát và vị trí phát Xóa toàn bộ lịch sử xem\? - Đã xóa lịch sử xem + Lịch sử xem đã bị xóa Xóa lịch sử tìm kiếm Xóa lịch sử của từ khóa tìm kiếm Xóa toàn bộ lịch sử tìm kiếm\? Đã xóa lịch sử tìm kiếm Không thể phát luồng này Đã xảy ra lỗi trình phát không thể khôi phục - Phục hồi từ lỗi trình phát + Phục hồi lại trình phát bị lỗi Trình phát ngoài không hỗ trợ các loại liên kết này Không tìm thấy luồng video nào Không tìm thấy luồng audio nào @@ -229,7 +229,7 @@ Lịch sử Lịch sử Bạn có muốn xóa mục này khỏi lịch sử tìm kiếm không? - Lần chơi cuối + Lần phát cuối Hầu hết phát Nội dung trang chính Trang trống @@ -244,9 +244,9 @@ Cảnh báo: Không thể nhập tất cả các tệp. Thao tác này sẽ ghi đè cài đặt hiện tại của bạn. Bạn cũng muốn nhập cài đặt? - Xu hướng + Thịnh hành Mới và hot - Tẩy xoá + Loại bỏ Chi tiết Cài đặt âm thanh Giữ để nối tiếp @@ -299,23 +299,24 @@ \n3. Nhấn chọn \"Bao gồm tất cả dữ liệu trên YouTube\", sau đó nhấn \"Bỏ chọn tất cả\", sau đó chỉ chọn mục \"đăng kí\" rồi nhấn OK \n4. Nhấn nút \"Bước tiếp theo\" rồi nhấn \"Tạo tệp xuất\" \n5. Nhấn nút \"Tải xuống\" khi nó xuất hiện -\n6. Từ file zip mới tải về, trích xuất file .json ra (thường nằm ở đường dẫn \"YouTube và YouTube Music/đăng kí/subscriptions.json\") rồi nhập vào đây. - Nhập hồ sơ SoundCloud bằng cách nhập URL hoặc ID của bạn: +\n6. Từ file zip mới tải về, trích xuất file .json ra (thường nằm ở đường dẫn \"YouTube và YouTube Music/đăng kí/subscriptions.json\") rồi nhập vào đây. +\n7. [Nếu nhập file .zip không thành công] Hãy giải nén tệp .csv (thường nó được để dưới phần \"YouTube and YouTube Music/subscriptions/subscriptions.csv\"), nhấn vào nút NHẬP TỆP ở phía bên dưới rồi chọn tệp csv đã được giải nén + Để nhập hồ sơ SoundCloud bằng cách nhập URL hoặc ID của bạn, hãy làm các bước như sau: \n -\n1. Bật \"chế độ màn hình\" trong trình duyệt web (trang web không khả dụng cho thiết bị di động) +\n1. Bật \"chế độ máy tính\" trong trình duyệt web (trang web không khả dụng cho thiết bị di động) \n2. Truy cập URL này: %1$s \n3. Đăng nhập khi được hỏi \n4. Sao chép URL tiểu sử mà bạn đã được chuyển hướng đến. - Hãy nhớ rằng hoạt động này có thể là mạng đắt tiền. + Hãy nhớ rằng hoạt động này có thể khiến bạn bị trừ tiền. \n -\nBạn có muốn tiếp tục? +\nBạn có muốn tiếp tục không\? Điều khiển tốc độ phát lại Speed Chiều cao - Hủy liên kết (có thể gây méo) + Bỏ gắn (có thể gây méo) Tua đi nhanh trong khi im lặng Tiếp theo - Cài lại + Đặt lại Để tuân thủ Quy định bảo vệ dữ liệu chung của châu Âu (GDPR), chúng tôi sẽ thu hút sự chú ý của bạn đến chính sách bảo mật của NewPipe. Vui lòng đọc kỹ. \nBạn phải chấp nhận nó để gửi cho chúng tôi báo cáo lỗi. Chấp nhận @@ -408,9 +409,9 @@ Chỉ một tải xuống sẽ chạy Bắt đầu tải xuống Tạm dừng tải xuống - Hỏi vị trí tải xuống - Bạn sẽ được hỏi vị trí lưu mỗi lần tải xuống. -\nHãy bật trình chọn thư mục của hệ thống (SAF) nếu bạn muốn tải xuống vào một chiếc thẻ SD ngoài + Hỏi nơi thư mục để tải xuống + Bạn sẽ được hỏi nơi lưu vào mỗi lần tải xuống. +\nHãy bật trình chọn thư mục của hệ thống (SAF) nếu bạn muốn tải xuống vào một cái thẻ nhớ Xóa vị trí phát Xóa toàn bộ vị trí phát Xác nhận xóa toàn bộ vị trí phát\? @@ -435,7 +436,7 @@ Có, và video đã xem một phần Video đã xem trước và sau khi được thêm vào playlist sẽ bị xóa. -\nBạn có chắc không\? Không thể hồi phục! +\nBạn có chắc không\? Video sẽ không thể hồi phục được! Xóa video đã xem\? Xóa đã xem Mặt định hệ thống @@ -471,7 +472,7 @@ Video này bị giới hạn độ tuổi. \n \nBật \"%1$s\" trong cài đặt nếu bạn muốn xem video này. - hế độ giới hạn YouTube + Bật chế độ hạn chế Youtube Chỉ URL HTTPS được hỗ trợ Chọn thực thể PeerTube ưa thích Thực thể PeerTube @@ -526,7 +527,7 @@ Không bao giờ Chỉ trên Wi-Fi Hành vi tự động phát — %s - Hàng đợi phát + Phát hàng đợi (Video) Không có danh sách nào ở đây Chọn danh sách Vui lòng kiểm tra xem vấn đề bạn đang gặp đã có báo cáo trước đó chưa. Nếu bạn tạo nhiều báo cáo trùng lặp, bạn sẽ làm tốn thời gian để chúng tôi đọc thay vì thực sự sửa lỗi. @@ -559,8 +560,8 @@ Kiểm tra bộ Hàng Xoá Cookie mà NewPipe lưu trữ sau khi bạn vượt - Cookie đã được - Xoá bỏ Cookie của + Cookie reCAPTCHA đã được xóa + Xóa bỏ Cookie của reCAPCHA YouTube cung cấp \"Chế độ hạn chế\" để ẩn nội dung người lớn Yêu cầu Android tùy chỉnh màu của thông báo theo màu chính của ảnh thu nhỏ (lưu ý rằng việc này không khả dụng trên tất cả thiết bị) Tô màu thông báo @@ -569,7 +570,7 @@ Gần đây Đang tính hash Mô tả - Stream liên quan + Video liên quan Bình luận Thông báo cho quá trình hash video Thông báo hash video @@ -578,16 +579,16 @@ Tắt để ẩn mô tả video và các thông tin bổ sung Hiện mô tả Mở bằng - Tệp đã bắt đầu được tải xuống - Bạn có thể chọn giao diện tối cho mình bên dưới - Chọn giao diện tối của bạn — %s - Tự động (theme hệ thống) + Tệp tin đã bắt đầu được tải xuống + Bạn có thể chọn giao diện ban đêm cho mình phía bên dưới + Chọn giao diện ban đêm của bạn — %s + Tự động (giao diện hệ thống) Radio Nội dung này chỉ dành cho người dùng trả phí, nên NewPipe không thể phát hay tải xuống. Video này chỉ được dành cho thành viên YouTube Music Premium, nên NewPipe không thể phát hay tải xuống. Nội dung này được để ở chế độ riêng tư, nên NewPipe không thể phát hay tải xuống. Đây là một track SoundCloud Go+, nên NewPipe không thể phát hay tải xuống được, ít nhất là tại quốc gia của bạn. - Nội dung này không có ở quốc gia của bạn. + Nội dung này không có sẳn ở quốc gia của bạn. Làm văng ứng dụng Video này đã bị giới hạn độ tuổi. \nVì những quy định mới của YouTube, NewPipe không thể phát được video này, vì không thể tìm thấy nguồn phát video. @@ -618,7 +619,7 @@ Bật chọn văn bản trong mô tả Bây giờ bạn có thể chọn văn bản trong mô tả. Lưu ý rằng trang có thể nhấp nháy và các liên kết có thể không nhấn vào được trong khi ở chế độ chọn. %s cung cấp lý do này: - Tài khoản bị chấm dứt + Tài khoản đã bị chấm dứt Hiện các mục đã xem Chế độ nguồn dữ liệu nhanh không cung cấp thêm thông tin về cái này. Tài khoản của tác giả đã bị chấm dứt. @@ -626,7 +627,7 @@ \nBạn có muốn huỷ đăng ký kênh này không\? Không thể tải nguồn dữ liệu cho \'%s\'. Lỗi khi tải nguồn dữ liệu - Bắt đầu từ Android 10, chỉ có \'Storage Access Framework\' được hỗ trợ + \'Storage Access Framework\' chỉ được hỗ trợ từ Android 10 trở đi \'Storage Access Framework\' không được hỗ trợ trên Android KitKat và cũ hơn Bạn sẽ được hỏi nơi bạn muốn lưu mỗi mục tải xuống Chưa có thư mục tải xuống nào được đặt, hãy chọn thư mục tải xuống mặc định ngay @@ -655,7 +656,7 @@ Đã cho mục tiếp vào hàng đợi Cho mục tiếp vào hàng đợi Đang thực hiện...Có thể mất một lúc - Thông báo báo cáo lỗi + Thông báo lỗi Thông báo để báo cáo lỗi NewPipe đã gặp lỗi, nhấn để báo cáo Có lỗi xảy ra, hãy xem thông báo @@ -672,4 +673,10 @@ \nVui lòng cài đặt ứng dụng quản lý tệp tương thích với Storage Access Framework. Không tìm thấy ứng dụng quản lý tệp phù hợp nào để thực hiện hành động. \nVui lòng cài đặt ứng dụng quản lý tệp hoặc tắt \'%s\' trong cài đặt tải xuống. + Thay đổi kích thước khoảng thời gian tải (tầm khoảng %s). Để ở giá trị thấp hơn có thể sẽ tăng tốc độ tải video hơn ban đầu. Khởi động lại trình phát để áp dụng thay đổi. + LeakCanary không khả dụng + Điều chỉnh cao độ theo nhạc nền âm nhạc + Nhịp độ tiếp theo + ExoPlayer mặc định + Bình luận được ghim \ No newline at end of file From fa141e394befb99aa5f9ec5438592fb4fc5415e6 Mon Sep 17 00:00:00 2001 From: ssantos Date: Sat, 26 Mar 2022 22:02:44 +0000 Subject: [PATCH 025/992] Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-pt-rPT/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 72f05896a..e443635dd 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -688,4 +688,8 @@ Comentário fixado Já está a reproduzir em segundo plano LeakCanary não está disponível + Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar. + Ajustar o tom por semitons musicais + Passo do tempo + Predefinido do ExoPlayer \ No newline at end of file From a8573f268b4f9d120a3bb6d0d1d0af213db201a6 Mon Sep 17 00:00:00 2001 From: Bob Date: Mon, 21 Mar 2022 10:11:25 +0000 Subject: [PATCH 026/992] Translated using Weblate (Swahili) Currently translated at 5.0% (31 of 617 strings) --- app/src/main/res/values-sw/strings.xml | 45 +++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml index a6b3daec9..b743a2825 100644 --- a/app/src/main/res/values-sw/strings.xml +++ b/app/src/main/res/values-sw/strings.xml @@ -1,2 +1,45 @@ - \ No newline at end of file + + Gusa kioo la kukuza ili kuanza. + Ni baadhi ya vifaa vingine pekee vinavyoweza kucheza video za 2K/4K + Cheza na Kodi + Onyesha chaguo la \"Cheza na Kodi\" + Onyesha chaguo la kucheza video kupitia kituo cha media cha Kodi + iliyochapishwa kwa %1$s + Hakuna stream player kilichopatikana. Sakinisha VLC\? + Hakuna stream player kilichopatikana(unaweza sakinisha VLC kuicheza). + Sakinisha + Cancel + Fungua katika mtandao + Fungua katika “popup mode” + Weka alama kuwa umetazama + Fungua na + Share + Download + Settings + Tafuta + Ulimaanisha \"%1$s\"\? + OK + Inaonyesha matokeo ya:%s + Tumia kicheza sauti cha nje + Kitufe cha kitendo cha kwanza + Fast-forward/-rewind seek duration + Kumbuka ukubwa wa mwisho na nafasi ya popup + Kitufe cha kitendo cha tano + Changanya + Rudia + Download stream file + Tumia kicheza video cha nje + Subscribe + Subscribed + Unsubscribe + Haikuweza kubadilisha \"subscription\" + Onyesha habari + Ongeza kwa + Kitufe cha kitendo cha tatu + Kitufe cha kitendo cha pili + Kitufe cha kitendo cha nne + Buffering + Hakuna + Use fast inexact seek + \ No newline at end of file From 3bbabb8416568ab70eb828e2c7e8d4160a0514d3 Mon Sep 17 00:00:00 2001 From: Agnieszka C Date: Sat, 2 Apr 2022 09:17:03 +0000 Subject: [PATCH 027/992] Translated using Weblate (Polish) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-pl/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 1b161a764..15d846a96 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -52,7 +52,7 @@ Nie udało się utworzyć menu pobierania Przepraszamy, to nie powinno mieć miejsca. Zgłoś błąd przez e-mail - Niestety, coś poszło nie tak. + Niestety, coś poszło nie tak Zgłoś Informacje: Co się stało: @@ -313,7 +313,7 @@ Usuwa historię wyszukiwania słów kluczowych Usunąć całą historię wyszukiwania\? Usunięto historię wyszukiwania - Usunięto jedną pozycję. + Usunięto jedną pozycję Brak zainstalowanej aplikacji do odtworzenia tego pliku NewPipe jest wolnym i bezpłatnym oprogramowaniem: Możesz używać, udostępniać i ulepszać je do woli. W szczególności możesz je redystrybuować i/lub modyfikować zgodnie z warunkami GNU General Public License, opublikowanej przez Free Software Fundation, w wersji 3 albo (według Twojego wyboru) jakiejkolwiek późniejszej wersji. Czy chcesz zaimportować również ustawienia? @@ -604,7 +604,7 @@ Otwórz za pomocą Żadna aplikacja na Twoim urządzeniu nie może tego otworzyć Powiązane pozycje - Awaria aplikacji + Zepsuj aplikację Ta zawartość dostępna jest tylko dla użytkowników, którzy za nią zapłacili. Nie może ona być strumieniowana ani pobierana przez NewPipe. To wideo dostępne jest tylko dla subskrybentów usługi YouTube Music Premium. Nie może ono być strumieniowane ani pobierane przez NewPipe. Ta zawartość jest prywatna, więc nie może być strumieniowana ani pobierana przez NewPipe From 0e73eb568e58cb261692aa5f6d83c173b4103642 Mon Sep 17 00:00:00 2001 From: Napstaguy04 Date: Mon, 4 Apr 2022 05:25:55 +0200 Subject: [PATCH 028/992] Added translation using Weblate (Tagalog) --- app/src/main/res/values-tl/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-tl/strings.xml diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/app/src/main/res/values-tl/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From 2e771cd65ab1eff2cb090351a4083d1d129fe53a Mon Sep 17 00:00:00 2001 From: GGAutomaton <32899400+GGAutomaton@users.noreply.github.com> Date: Mon, 4 Apr 2022 23:58:39 +0800 Subject: [PATCH 029/992] Fix crash when rotating device on unsupported channels --- .../fragments/list/channel/ChannelFragment.java | 13 ++++++++++++- 1 file changed, 12 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 869503b5b..6ad811320 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 @@ -77,6 +77,8 @@ public class ChannelFragment extends BaseListInfoFragment Date: Sun, 3 Apr 2022 19:10:49 +0000 Subject: [PATCH 030/992] Translated using Weblate (French) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-fr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5eb543ccc..fa23d7e66 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -693,5 +693,5 @@ Modifie la taille de l\'intervalle de chargement (actuellement %s). Une valeur plus faible peut accélérer le chargement initial des vidéos . Règler la hauteur par demi-tons musicaux Pas du tempo - ExoPlayer par défaut + Valeur par défaut d’ExoPlayer \ No newline at end of file From f9af698521a5eb47eff74e6bcc057ae59bb53fa0 Mon Sep 17 00:00:00 2001 From: Muhammad Fahim Zunayed Date: Mon, 4 Apr 2022 09:34:32 +0000 Subject: [PATCH 031/992] Translated using Weblate (Bengali (Bangladesh)) Currently translated at 51.3% (317 of 617 strings) --- app/src/main/res/values-bn-rBD/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 807de8735..6fa45249e 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -2,10 +2,10 @@ অনুসন্ধান এ চাপ দিয়ে শুরু করুন প্রকাশকাল %1$s - কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। VLC ইনস্টল করতে চাও\? - ইনস্টল - বাদ দিন - ব্রাউজারে ওপেন করো + কোন স্ট্রিম প্লেয়ার পাওয়া যায়নি। VLC ইনস্টল করতে চান\? + ইনস্টল করুন + বাতিল করুন + ওয়েব ব্রাউজারে ওপেন করুন পপ-আপ মোডে ওপেন করো শেয়ার ডাউনলোউড From 229481c89c24438a5de52f9d2380cec7c0ce2944 Mon Sep 17 00:00:00 2001 From: Napstaguy04 Date: Mon, 4 Apr 2022 03:25:09 +0000 Subject: [PATCH 032/992] Translated using Weblate (Filipino) Currently translated at 19.1% (118 of 617 strings) --- app/src/main/res/values-fil/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index 8386c895f..c0197c3cf 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -12,7 +12,7 @@ I-download I-download ang stream file Maghanap - Pagsasaayos + Ayos ng App Ibig mo bang sabihin \"%1$s\"\? Ibahagi sa Gumamit ng ibang video player @@ -115,4 +115,6 @@ Tandaan ang huling laki at posisyon ng popup Gamitin ang mabilis ngunit di-saktong seek Haba ng fast forward/-rewind seek + Ituloy ang pagpapalabas + Mga Patok Ngayon \ No newline at end of file From 984d19a9a53a4374326e932cac5f39d90e5ad533 Mon Sep 17 00:00:00 2001 From: Napstaguy04 Date: Mon, 4 Apr 2022 03:36:08 +0000 Subject: [PATCH 033/992] Translated using Weblate (Tagalog) Currently translated at 9.5% (59 of 617 strings) --- app/src/main/res/values-tl/strings.xml | 68 +++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index a6b3daec9..82e0feaac 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -1,2 +1,68 @@ - \ No newline at end of file + + Maliwanag + Madilim + Ang video na ito ay may paghihigpit sa edad. +\n +\nDahil sa mga bagong polisiya ng Youtube, hindi maaring ma-access ng NewPipe ang mga video streams nito, kaya hindi ito maipapalabas. + Di-mabago ang subscription + Patayin upang maitago ang mga puna + I-tanda bilang napanood na + Ibahagi + I-download + I-download ang stream file + Ayos ng App + Ibig mo bang sabihin \"%1$s\"\? + Ipinapakita ang mga resulta para sa: %s + Buksan sa + Pindutin ang magnifiying glass upang magsimula. + Inilathala noong %1$s + Sige + Iyong puna (sa Ingles): + Tinatanggal ang audio sa ilang mga resolusyon + Mag-subscribe + Naka-unsubscribe na sa channel + Likuran + Ipakita ang paglalarawan + Mga subscription + Maitim + Ipakita ang paglalarawan + Mga mungkahi sa paghahanap + Ituloy ang pagpapalabas + Kasaysayan ng napanood + Linisin ang data + Kusang pagpapalabas + Mga Download + Mga Download + Lahat + Mga Channel + Mga Listahan ng Ipapalabas + Mga Video + Mga Track + Mga Gumagamit + Mga Kaganapan + Mga Awit + Mga Album + Walang puna + Hindi pinapahintulot ang pagpuna + Wala pang nakapanood + + %s panonood + Mga %s panonood + + Mga Patok Ngayon + Ipakita ang mga puna + Ipagpaliban + Buksan sa browser + Maghanap + Ibahagi sa + Gumamit ng ibang video player + Mga Puna + Hindi mai-karga ang mga puna + Naka-pin na puna + Patayin upang itago ang paglalarawan ng video at ang karagdagang kaalaman nito + Ang video na ito ay may paghihigpit sa edad. +\n +\nBuksan ang \"%1$s\" sa ayos ng app kung gusto mong makita ito. + Mga Artista + \ No newline at end of file From 724eac9168356eac08b9389ae57c9af7ef9cfe1b Mon Sep 17 00:00:00 2001 From: karyogamy Date: Mon, 4 Apr 2022 20:17:33 -0400 Subject: [PATCH 034/992] fixed: player caption auto-selection not reflected in gui. fixed: player caption selection skipping on multiple language variants. --- .../org/schabi/newpipe/player/Player.java | 89 +++++++++---------- 1 file changed, 43 insertions(+), 46 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 2c8be5d78..bb3997a81 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -62,7 +62,6 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParamete import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; -import static org.schabi.newpipe.util.Localization.containsCaseInsensitive; import static java.util.concurrent.TimeUnit.MILLISECONDS; import android.animation.Animator; @@ -130,6 +129,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player.PositionInfo; @@ -138,7 +138,6 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.TrackGroup; -import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; @@ -2530,7 +2529,7 @@ public final class Player implements Log.d(TAG, "ExoPlayer - onTracksChanged(), " + "track group size = " + tracksInfo.getTrackGroupInfos().size()); } - onTextTracksChanged(); + onTextTracksChanged(tracksInfo); } @Override @@ -3516,17 +3515,7 @@ public final class Player implements return; } captionPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_CAPTION); - - final String userPreferredLanguage = - prefs.getString(context.getString(R.string.caption_user_set_key), null); - /* - * only search for autogenerated cc as fallback - * if "(auto-generated)" was not already selected - * we are only looking for "(" instead of "(auto-generated)" to hopefully get all - * internationalized variants such as "(automatisch-erzeugt)" and so on - */ - boolean searchForAutogenerated = userPreferredLanguage != null - && !userPreferredLanguage.contains("("); + captionPopupMenu.setOnDismissListener(this); // Add option for turning off caption final MenuItem captionOffItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, @@ -3549,6 +3538,9 @@ public final class Player implements captionItem.setOnMenuItemClickListener(menuItem -> { final int textRendererIndex = getCaptionRendererIndex(); if (textRendererIndex != RENDERER_UNAVAILABLE) { + // DefaultTrackSelector will select for tracks with similar language names + // if a track of userPreferredLanguage is not found + // This means (auto-generated) will be resolved automatically. trackSelector.setParameters(trackSelector.buildUponParameters() .setPreferredTextLanguage(captionLanguage) .setRendererDisabled(textRendererIndex, false)); @@ -3557,22 +3549,23 @@ public final class Player implements } return true; }); - // apply caption language from previous user preference - if (userPreferredLanguage != null - && (captionLanguage.equals(userPreferredLanguage) - || (searchForAutogenerated && captionLanguage.startsWith(userPreferredLanguage)) - || (userPreferredLanguage.contains("(") && captionLanguage.startsWith( - userPreferredLanguage.substring(0, userPreferredLanguage.indexOf('(')))))) { - final int textRendererIndex = getCaptionRendererIndex(); - if (textRendererIndex != RENDERER_UNAVAILABLE) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setPreferredTextLanguage(captionLanguage) - .setRendererDisabled(textRendererIndex, false)); - } - searchForAutogenerated = false; - } } - captionPopupMenu.setOnDismissListener(this); + + // apply caption language from previous user preference + final List selectedPreferredLanguages = + trackSelector.getParameters().preferredTextLanguages; + final String userPreferredLanguage = + prefs.getString(context.getString(R.string.caption_user_set_key), null); + final int textRendererIndex = getCaptionRendererIndex(); + + if (userPreferredLanguage != null + && availableLanguages.contains(userPreferredLanguage) + && !selectedPreferredLanguages.contains(userPreferredLanguage) + && textRendererIndex != RENDERER_UNAVAILABLE) { + trackSelector.setParameters(trackSelector.buildUponParameters() + .setPreferredTextLanguage(userPreferredLanguage) + .setRendererDisabled(textRendererIndex, false)); + } } /** @@ -3668,41 +3661,45 @@ public final class Player implements binding.subtitleView.setStyle(captionStyle); } - private void onTextTracksChanged() { - final int textRenderer = getCaptionRendererIndex(); - + private void onTextTracksChanged(@NonNull final TracksInfo currentTrackInfo) { if (binding == null) { return; } + if (trackSelector.getCurrentMappedTrackInfo() == null - || textRenderer == RENDERER_UNAVAILABLE) { + || !currentTrackInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_TEXT)) { binding.captionTextView.setVisibility(View.GONE); return; } - final TrackGroupArray textTracks = trackSelector.getCurrentMappedTrackInfo() - .getTrackGroups(textRenderer); - // Extract all loaded languages - final List availableLanguages = new ArrayList<>(textTracks.length); - for (int i = 0; i < textTracks.length; i++) { - final TrackGroup textTrack = textTracks.get(i); + final List textTracks = currentTrackInfo + .getTrackGroupInfos() + .stream() + .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getTrackType()) + .collect(Collectors.toList()); + final List availableLanguages = new ArrayList<>(textTracks.size()); + for (int i = 0; i < textTracks.size(); i++) { + final TrackGroup textTrack = textTracks.get(i).getTrackGroup(); if (textTrack.length > 0) { availableLanguages.add(textTrack.getFormat(0).language); } } - // Normalize mismatching language strings - final String preferredLanguage = trackSelector.getParameters() - .preferredTextLanguages.stream().findFirst().orElse(null); + // Find selected text track + final Optional selectedTracks = textTracks.stream() + .filter(TracksInfo.TrackGroupInfo::isSelected) + .filter(info -> info.getTrackGroup().length >= 1) + .map(info -> info.getTrackGroup().getFormat(0)) + .findFirst(); + // Build UI buildCaptionMenu(availableLanguages); - if (trackSelector.getParameters().getRendererDisabled(textRenderer) - || preferredLanguage == null || (!availableLanguages.contains(preferredLanguage) - && !containsCaseInsensitive(availableLanguages, preferredLanguage))) { + if (trackSelector.getParameters().getRendererDisabled(getCaptionRendererIndex()) + || !selectedTracks.isPresent()) { binding.captionTextView.setText(R.string.caption_none); } else { - binding.captionTextView.setText(preferredLanguage); + binding.captionTextView.setText(selectedTracks.get().language); } binding.captionTextView.setVisibility( availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); From 55fc3fc177497f484b2aa13440e731a3f6d28243 Mon Sep 17 00:00:00 2001 From: karyogamy Date: Fri, 8 Apr 2022 18:21:30 -0400 Subject: [PATCH 035/992] added: caption language stem utility to support language variant conversion between videos. --- .../java/org/schabi/newpipe/player/Player.java | 7 +++++-- .../newpipe/player/helper/PlayerHelper.java | 15 +++++++++++++++ 2 files changed, 20 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 bb3997a81..9f1fda981 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -46,6 +46,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZ import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; +import static org.schabi.newpipe.player.helper.PlayerHelper.captionLanguageStemOf; import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; @@ -3542,7 +3543,8 @@ public final class Player implements // if a track of userPreferredLanguage is not found // This means (auto-generated) will be resolved automatically. trackSelector.setParameters(trackSelector.buildUponParameters() - .setPreferredTextLanguage(captionLanguage) + .setPreferredTextLanguages(captionLanguage, + captionLanguageStemOf(captionLanguage)) .setRendererDisabled(textRendererIndex, false)); prefs.edit().putString(context.getString(R.string.caption_user_set_key), captionLanguage).apply(); @@ -3563,7 +3565,8 @@ public final class Player implements && !selectedPreferredLanguages.contains(userPreferredLanguage) && textRendererIndex != RENDERER_UNAVAILABLE) { trackSelector.setParameters(trackSelector.buildUponParameters() - .setPreferredTextLanguage(userPreferredLanguage) + .setPreferredTextLanguages(userPreferredLanguage, + captionLanguageStemOf(userPreferredLanguage)) .setRendererDisabled(textRendererIndex, false)); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 6a7c27bdc..e21b21d35 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -144,6 +144,21 @@ public final class PlayerHelper { ? " (" + context.getString(R.string.caption_auto_generated) + ")" : ""); } + @NonNull + public static String captionLanguageStemOf(@NonNull final String language) { + if (!language.contains("(") || !language.contains(")")) { + return language; + } + + if (language.startsWith("(")) { + // language text is right-to-left + final String[] parts = language.split("\\)"); + return parts[parts.length - 1].trim(); + } + + return language.split("\\(")[0].trim(); + } + @NonNull public static String resizeTypeOf(@NonNull final Context context, @ResizeMode final int resizeMode) { From 9818f179c49d0b12aa49364ef16251c4ea8c126f Mon Sep 17 00:00:00 2001 From: karyogamy Date: Mon, 11 Apr 2022 22:06:43 -0400 Subject: [PATCH 036/992] fixed: auto-generated captions to have lower selection priority as manual captions. --- .../org/schabi/newpipe/player/Player.java | 33 ++++++++++--------- .../resolver/VideoPlaybackResolver.java | 5 +++ 2 files changed, 22 insertions(+), 16 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 9f1fda981..82ae0df27 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -46,7 +46,6 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZ import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; -import static org.schabi.newpipe.player.helper.PlayerHelper.captionLanguageStemOf; import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; @@ -138,7 +137,6 @@ import com.google.android.exoplayer2.RenderersFactory; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.TracksInfo; import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; @@ -212,7 +210,6 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.views.ExpandableSurfaceView; import org.schabi.newpipe.views.player.PlayerFastSeekOverlay; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -3539,12 +3536,18 @@ public final class Player implements captionItem.setOnMenuItemClickListener(menuItem -> { final int textRendererIndex = getCaptionRendererIndex(); if (textRendererIndex != RENDERER_UNAVAILABLE) { - // DefaultTrackSelector will select for tracks with similar language names - // if a track of userPreferredLanguage is not found - // This means (auto-generated) will be resolved automatically. + // DefaultTrackSelector will select for text tracks in the following order. + // When multiple tracks share the same rank, a random track will be chosen. + // 1. ANY track exactly matching preferred language name + // 2. ANY track exactly matching preferred language stem + // 3. ROLE_FLAG_CAPTION track matching preferred language stem + // 4. ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND track matching preferred language stem + // This means if a caption track of preferred language is not available, + // then an auto-generated track of that language will be chosen automatically. trackSelector.setParameters(trackSelector.buildUponParameters() .setPreferredTextLanguages(captionLanguage, - captionLanguageStemOf(captionLanguage)) + PlayerHelper.captionLanguageStemOf(captionLanguage)) + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) .setRendererDisabled(textRendererIndex, false)); prefs.edit().putString(context.getString(R.string.caption_user_set_key), captionLanguage).apply(); @@ -3561,12 +3564,12 @@ public final class Player implements final int textRendererIndex = getCaptionRendererIndex(); if (userPreferredLanguage != null - && availableLanguages.contains(userPreferredLanguage) && !selectedPreferredLanguages.contains(userPreferredLanguage) && textRendererIndex != RENDERER_UNAVAILABLE) { trackSelector.setParameters(trackSelector.buildUponParameters() .setPreferredTextLanguages(userPreferredLanguage, - captionLanguageStemOf(userPreferredLanguage)) + PlayerHelper.captionLanguageStemOf(userPreferredLanguage)) + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) .setRendererDisabled(textRendererIndex, false)); } } @@ -3681,13 +3684,11 @@ public final class Player implements .stream() .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getTrackType()) .collect(Collectors.toList()); - final List availableLanguages = new ArrayList<>(textTracks.size()); - for (int i = 0; i < textTracks.size(); i++) { - final TrackGroup textTrack = textTracks.get(i).getTrackGroup(); - if (textTrack.length > 0) { - availableLanguages.add(textTrack.getFormat(0).language); - } - } + final List availableLanguages = textTracks.stream() + .map(TracksInfo.TrackGroupInfo::getTrackGroup) + .filter(textTrack -> textTrack.length > 0) + .map(textTrack -> textTrack.getFormat(0).language) + .collect(Collectors.toList()); // Find selected text track final Optional selectedTracks = textTracks.stream() diff --git a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java index 565f0b23e..1aa7a5a18 100644 --- a/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java +++ b/app/src/main/java/org/schabi/newpipe/player/resolver/VideoPlaybackResolver.java @@ -6,6 +6,7 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; @@ -116,9 +117,13 @@ public class VideoPlaybackResolver implements PlaybackResolver { if (mimeType == null) { continue; } + final @C.RoleFlags int textRoleFlag = subtitle.isAutoGenerated() + ? C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND + : C.ROLE_FLAG_CAPTION; final MediaItem.SubtitleConfiguration textMediaItem = new MediaItem.SubtitleConfiguration.Builder(Uri.parse(subtitle.getUrl())) .setMimeType(mimeType) + .setRoleFlags(textRoleFlag) .setLanguage(PlayerHelper.captionLanguageOf(context, subtitle)) .build(); final MediaSource textSource = dataSource From 601bc967349c3f1bfc8e9c0855bafad1906fb737 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 12 Apr 2022 22:09:45 +0200 Subject: [PATCH 037/992] Updated to latest version of the Extractor-dev-branch --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index d3f6c5d06..95d1c98af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:5a1873084' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:2e92d718a2' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" From cb1fa8b5ae38e49e528625d63ce00fd6089c69b9 Mon Sep 17 00:00:00 2001 From: Luciano dos Santos Gonzalez Date: Sat, 9 Apr 2022 12:34:04 +0000 Subject: [PATCH 038/992] Translated using Weblate (Portuguese) Currently translated at 100.0% (617 of 617 strings) --- app/src/main/res/values-pt/strings.xml | 54 +++++++++++++------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index f40b8a478..2fd87d0b3 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,21 +1,21 @@ Publicado em %1$s - Não tem um reprodutor de vídeo. Instalar o VLC\? + Reprodutor de vídeo não encontrado. Instalar o VLC\? Instalar Cancelar Abrir no navegador - Partilhar - Descarregar + Compartilhar + Baixar Pesquisar Definições - Será que queria dizer \"%1$s\"\? - Partilhar com + Você quis dizer \"%1$s\"\? + Compartilhar com Utilizar reprodutor de vídeo externo Utilizar reprodutor de áudio externo - Pasta para os ficheiros de vídeo - Os ficheiros de vídeo descarregados serão guardados aqui - Escolha a pasta para colocar os ficheiros de vídeo + Pasta para os arquivos de vídeo + Os arquivos de vídeo baixados serão guardados aqui + Escolha a pasta para colocar os arquivos de vídeo Resolução padrão Reproduzir no Kodi Instalar Kore\? @@ -38,9 +38,9 @@ Aparência Reprodução em segundo plano Erro de rede - Pasta para ficheiros de áudio - Os ficheiros de áudio descarregados serão guardados aqui - Escolha a pasta para colocar os ficheiros de áudio + Pasta para arquivos de áudio + Os arquivos de áudio baixados serão guardados aqui + Escolha a pasta para colocar os arquivos de áudio Erro Não foi possível carregar todas as miniaturas Não foi possível decifrar a assinatura do URL @@ -60,7 +60,7 @@ Vídeo Áudio Tentar novamente - Toque na lupa para começar. + Toque nesta belíssima lupa para começar. Em direto Transferências Transferências @@ -96,7 +96,7 @@ Resolução padrão para janela popup Mostrar resoluções mais altas Apenas alguns dispositivos conseguem reproduzir vídeos em 2K/4K - popup + Pop-up Lembrar propriedades do popup Limpar Segundo plano @@ -116,12 +116,12 @@ Se tem ideias para: tradução, alterações de desenho, limpeza de código, ou alterações significativas no código fonte - todas as ajudas são bem-vindas. Quanto mais se faz, melhor ficará! Ver licença Participar - Subscrever - Subscrito - Canal não subscrito - Não foi possível alterar a subscrição - Não foi possível atualizar a subscrição - Subscrições + Inscrever-se + Inscrito + Canal não inscrito + Não foi possível alterar a inscrição + Não foi possível atualizar a inscrição + Inscrições Novidades Histórico de pesquisa Guardar termos de pesquisa localmente @@ -197,7 +197,7 @@ Mudar nome Doar Não foi encontrado um reprodutor (pode instalar o VLC para reproduzir). - Descarregar ficheiro de vídeo + Baixar arquivo de vídeo Adicionar a Utilizar pesquisa rápida A pesquisa inexata permite que esta seja mais rápida, mas reduz a precisão. Procurar por 5, 15 ou 25 segundos não funciona corretamente @@ -333,11 +333,11 @@ Nenhuma Ativar reprodutor em segundo plano Ativar reprodutor \'popup\' - Cancelar subscrição + Desinscrever-se Escolher separador - Gestos para controlo de volume + Gestos para controle de volume Utilizar gestos para controlar o volume do reprodutor - Gestos para controlo de brilho + Gestos para controle de brilho Utilizar gestos para controlar o brilho do reprodutor Atualizações Ficheiro eliminado @@ -539,21 +539,21 @@ Verifique se o seu erro já foi reportado. A criação de erros em duplicado tira-nos tempo que pode ser utilizado para corrigir os erros. Reportar no GitHub Copiar relatório formatado - A mostrar resultados para: %s + Mostrando resultados para: %s Quarto botão de ação Terceiro botão de ação Segundo botão de ação Primeiro botão de ação - Ajustar miniatura de vídeo mostrada na notificação de 16:9 para 1:1 (pode introduzir distorções) + Ajustar miniatura de vídeo mostrada na notificação de 16:9 para 1:1 (pode haver distorções) Ajustar miniatura à proporção de 1:1 Iniciar reprodução automaticamente — %s Reproduzir fila Nunca - A carregar + Carregando A fila do reprodutor ativo será substituída URL não reconhecido. Abrir com outra aplicação\? Enfileiramento automático - Baralhar + Embaralhar Notificação Apenas em Wi-Fi Nada From b6368b12962890cf8de938580958aec44218840a Mon Sep 17 00:00:00 2001 From: WB Date: Sat, 9 Apr 2022 23:43:35 +0000 Subject: [PATCH 039/992] Translated using Weblate (Galician) Currently translated at 98.5% (608 of 617 strings) --- app/src/main/res/values-gl/strings.xml | 38 ++++++++++++++++++++------ 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index bfe9710f4..28a3f77c8 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -64,7 +64,7 @@ Colocar a seguinte emisión na cola automaticamente Continúa rematando (non se repite) a cola de reprodución engadindo unha transmisión relacionada Suxestións de procura - Mostrar suxestións ao procurar + Escolla suxestións a mostrar ao procurar Historial de procura Gardar os termos de pesquisa localmente Ver o historial @@ -79,8 +79,8 @@ País predeterminado para o contido Reprodutor Comportamento - Vídeo & audio - Historial & caché + Vídeo e audio + Historial e caché Aparencia Depuración Reproducindo en segundo plano @@ -295,14 +295,15 @@ Exportación anterior Non foi posíbel importar as subscricións Non foi posíbel exportar as subscricións - Pode importar as súas subscricións de YouTube do Google takeout: + Importe as súas subscricións de Google takeout: \n \n1. Acceda ao URL %1$s \n2. Inicie a sesión cando lle for solicitado \n3. Faga click en \"Todos os datos incluídos\", logo en \"Desmarcar todo\", escolla só \"subscricións\", e prema \"OK\" \n4. Pulse en \"Próximo paso\" e escolla \"Crear exportación\" -\n5. Faga click no botón de \"Decarregar\" despois de que apareza e -\n6. Do arquivo zip decarregado extraia o .json (normalmente en \"YouTube e YouTube Music/subscricións/subscricións.json\") e impórteo aquí. +\n5. Faga click no botón de \"Decarregar\" despois de que apareza +\n6. Faga click en IMPORTAR ARQUIVO abaixo e seleccione o ficheiro .zip descarregado +\n7. [Se a importación do .zip fallar] Extraia o ficheiro .csv (xeralmente en Youtube e Youtube Music/subscriptions/subscriptions.csv), e faga click en IMPORTAR FICHEIRO abaixo e seleccione o .csv extraído Pode importar un perfil do SoundCloud escribindo o URL ou o seu ID: \n \n1. Active o «modo desktop» nun navegador da Internet (o sitio non está dispoñíbel para dispositivos móbiles) @@ -403,7 +404,7 @@ Lingua do aplicativo Elixir unha instancia O \'Framework Access Framework\' permite a descarga a unha tarxeta SD externa - Usar SAF + Usar selector de cartafois do sistema (SAF) Preguntaralle onde gardar cada descarga. \nPermita o Sistema de Escolla de Cartafois (SAF) se desexar descarregar para un SD externo Pregunta onde se descarga @@ -444,7 +445,7 @@ Xera un nome único Fallou a descarga Acción denegada polo sistema - En cola + Enfileirar recuperando post-procesamento en cola @@ -609,7 +610,7 @@ Este contido é privado, polo que non pode ser transmitido nin descarregado polo NewPipe. Non posúe ningunha aplicación para abrir isto Usar miniaturas para a pantalla de bloqueo e para as notificacións - Mostrar elementos reproducidos + Mostrar elementos vistos Os comentarios están desactivados Mostrar contido potencialmente non apto para menores porque ten unha limitación de idade (como +18) Definir cor da notificación @@ -671,4 +672,23 @@ Notificacións para reportar erros NewPipe atopou un erro, presione para reportar Comentario fixado + Enfileirado + Procurar actualizacións + Procurar manualmente novas versións + Axustar o ton do semitóns musicais + Paso do tempo + A procurar actualizacións… + A partir do Android 10, só o \'Sistema de Acceso ao Almacenamento\' está soportado + Cambia o tamaño do intervalo de carga (actualmente %s). Un valor menor pode acelerar o carregamento do vídeo. Cambios poden precisar un reinicio do reprodutor. + Procesando... Pode devagar un momento + Crear unha notificación de erro + Amosar fitas coloridas de Picasso na cima das imaxes que indican a súa fonte: vermello para a rede, azul para o disco e verde para a memoria + O \'Sistema de Acceso ao almacenamento\' non está soportado en Android KitKat e anteriores + Novos elementos + Predefinido do ExoPlayer + Amosar \"Travar o reprodutor\" + Amosar un snackbar de erro + Amosa unha opción de travamento ao usar o reprodutor + Travar o reprodutor + LeakCanary non está dispoñíbel \ No newline at end of file From 931906c9f3fb8e90dc965eb59f18f7c49d1db140 Mon Sep 17 00:00:00 2001 From: Subham Jena Date: Sat, 9 Apr 2022 14:39:16 +0000 Subject: [PATCH 040/992] Translated using Weblate (Odia) Currently translated at 3.8% (24 of 617 strings) --- app/src/main/res/values-or/strings.xml | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index edf808dca..be3ee95e4 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -1,4 +1,39 @@ ଆରମ୍ଭ କରିବା ପାଇଁ \"ସର୍ଚ୍ଚ\" ବଟନ କୁ ଦବାନ୍ତୁ + ସିଷ୍ଟମ୍ ଡିଫଲ୍ଟ + ନାମ ବଦଳାନ୍ତୁ + ବନ୍ଦ + ଭାଷା + ସମର୍ଥନ + ରାତ୍ରି ଥିମ୍ + ହାଲୁକା + କଳା + ଇତିବୃତ୍ତି + ଇତିବୃତ୍ତି + ଥିମ୍ + ଗାଢ଼ + + %d ମିନିଟ୍ + %d ମିନିଟ୍ + + ଆପ୍ ଭାଷା + ସ୍ୱୟଂଚାଳିତ (ଡିଭାଇସ୍ ଥିମ୍) + ଗୋପନୀୟତା + ନାମ + ବିବରଣୀ + ଚାଲୁ + + %d ଘଣ୍ଟା + %d ଘଣ୍ଟା + + + %d ସେକେଣ୍ଡ + %d ସେକେଣ୍ଡ + + + %d ଦିନ + %d ଦିନ + + ନୂଆ \ No newline at end of file From c85936bb111f39f2ff78843c5018d2dc6c3a3e99 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Apr 2022 15:43:56 -0400 Subject: [PATCH 041/992] Update action dependencies in workflows --- .github/workflows/ci.yml | 18 +++++++++--------- .github/workflows/image-minimizer.yml | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7dbfadc0b..13a4d8723 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: build-and-test-jvm: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: gradle/wrapper-validation-action@v1 - name: create and checkout branch @@ -40,7 +40,7 @@ jobs: run: git checkout -B ${{ github.head_ref }} - name: set up JDK 11 - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: java-version: 11 distribution: "temurin" @@ -50,7 +50,7 @@ jobs: run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint - name: Upload APK - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: app path: app/build/outputs/apk/debug/*.apk @@ -64,10 +64,10 @@ jobs: # api-level 19 is min sdk, but throws errors related to desugaring api-level: [ 21, 29 ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: set up JDK 11 - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: java-version: 11 distribution: "temurin" @@ -82,7 +82,7 @@ jobs: script: ./gradlew connectedCheck --stacktrace - name: Upload test report when tests fail # because the printed out stacktrace (console) is too short, see also #7553 - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: failure() with: name: android-test-report-api${{ matrix.api-level }} @@ -91,19 +91,19 @@ jobs: sonar: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Set up JDK 11 - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: java-version: 11 # Sonar requires JDK 11 distribution: "temurin" cache: 'gradle' - name: Cache SonarCloud packages - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar diff --git a/.github/workflows/image-minimizer.yml b/.github/workflows/image-minimizer.yml index 77b1faecf..c6ab6d5b3 100644 --- a/.github/workflows/image-minimizer.yml +++ b/.github/workflows/image-minimizer.yml @@ -11,9 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: node-version: 16 @@ -21,7 +21,7 @@ jobs: run: npm i probe-image-size@7.2.3 --ignore-scripts - name: Minimize simple images - uses: actions/github-script@v5 + uses: actions/github-script@v6 timeout-minutes: 3 with: script: | From 5ccf2d7bcce4444d593ce7e0f914cea43da30aa1 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Apr 2022 15:52:01 -0400 Subject: [PATCH 042/992] Reformat heart and seek-triangle drawables --- app/src/main/res/drawable/ic_heart.xml | 6 +++--- .../main/res/drawable/ic_play_seek_triangle.xml | 14 ++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/drawable/ic_heart.xml b/app/src/main/res/drawable/ic_heart.xml index 86d1f0527..248f9788b 100644 --- a/app/src/main/res/drawable/ic_heart.xml +++ b/app/src/main/res/drawable/ic_heart.xml @@ -2,9 +2,9 @@ android:width="24dp" android:height="24dp" android:tint="#E53935" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z" /> diff --git a/app/src/main/res/drawable/ic_play_seek_triangle.xml b/app/src/main/res/drawable/ic_play_seek_triangle.xml index 1aee026db..9c257c423 100644 --- a/app/src/main/res/drawable/ic_play_seek_triangle.xml +++ b/app/src/main/res/drawable/ic_play_seek_triangle.xml @@ -1,11 +1,9 @@ - + android:width="16dp" + android:height="20dp" + android:viewportWidth="24" + android:viewportHeight="24"> - + android:fillColor="#FFFFFF" + android:pathData="M3,2 L22,12 L3,22 Z" /> From ef5c71374becc37ed1c9a1236f73939b105ba2bc Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Apr 2022 15:52:14 -0400 Subject: [PATCH 043/992] Replace checklist menu drawable --- app/src/main/res/drawable/ic_checklist.xml | 10 ++++++++++ app/src/main/res/drawable/ic_list_check.xml | 10 ---------- app/src/main/res/menu/menu_notifications_channels.xml | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 app/src/main/res/drawable/ic_checklist.xml delete mode 100644 app/src/main/res/drawable/ic_list_check.xml diff --git a/app/src/main/res/drawable/ic_checklist.xml b/app/src/main/res/drawable/ic_checklist.xml new file mode 100644 index 000000000..27bed183f --- /dev/null +++ b/app/src/main/res/drawable/ic_checklist.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_list_check.xml b/app/src/main/res/drawable/ic_list_check.xml deleted file mode 100644 index 37d806044..000000000 --- a/app/src/main/res/drawable/ic_list_check.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/menu_notifications_channels.xml b/app/src/main/res/menu/menu_notifications_channels.xml index 79b9cd7c1..857e89372 100644 --- a/app/src/main/res/menu/menu_notifications_channels.xml +++ b/app/src/main/res/menu/menu_notifications_channels.xml @@ -4,7 +4,7 @@ \ No newline at end of file From 3a419126f31d7634497c818cd8195763dd440b21 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Apr 2022 16:50:28 -0400 Subject: [PATCH 044/992] Use simpler DrawerLayout method --- app/src/main/java/org/schabi/newpipe/MainActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 6ae5cf936..fcb9d9725 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -721,7 +721,7 @@ public class MainActivity extends AppCompatActivity { if (toggle != null) { toggle.syncState(); toolbarLayoutBinding.toolbar.setNavigationOnClickListener(v -> mainBinding.getRoot() - .openDrawer(GravityCompat.START)); + .open()); mainBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_UNDEFINED); } } else { From 7fb2973431652754e69c1bba10115f2d92dd2612 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Apr 2022 21:10:29 -0400 Subject: [PATCH 045/992] Update AGP, Gradle, and Kotlin --- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 297722a63..e40c35108 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.6.10' + ext.kotlin_version = '1.6.20' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:7.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 00b6916f1..4ed3bdea4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip -distributionSha256Sum=cd5c2958a107ee7f0722004a12d0f8559b4564c34daad7df06cffd4d12a426d0 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip +distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 248ca5ee120ef3b446f5e91bd81242e407543256 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Apr 2022 22:08:42 -0400 Subject: [PATCH 046/992] Update ACRA library --- app/build.gradle | 2 +- app/src/main/java/org/schabi/newpipe/App.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 95d1c98af..c0489e384 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -262,7 +262,7 @@ dependencies { implementation "com.nononsenseapps:filepicker:4.2.1" // Crash reporting - implementation "ch.acra:acra-core:5.8.4" + implementation "ch.acra:acra-core:5.9.1" // Properly restarting implementation 'com.jakewharton:process-phoenix:2.1.2' diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 6b02e21ca..70c947478 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -205,7 +205,7 @@ public class App extends MultiDexApplication { return; } - final CoreConfigurationBuilder acraConfig = new CoreConfigurationBuilder(this) + final CoreConfigurationBuilder acraConfig = new CoreConfigurationBuilder() .withBuildConfigClass(BuildConfig.class); ACRA.init(this, acraConfig); } From 5e6752db14bee50c58d3fbd90e674ada4f546788 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 15 Apr 2022 10:54:31 +0200 Subject: [PATCH 047/992] Update NewPipeExtractor for hotfix release --- app/build.gradle | 2 +- .../schabi/newpipe/fragments/detail/DescriptionFragment.java | 2 +- .../schabi/newpipe/settings/PeertubeInstanceListFragment.java | 2 +- .../main/java/us/shandian/giga/get/DownloadMissionRecover.java | 2 +- app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 633da37bd..d8467d1e0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -189,7 +189,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.14' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:bebdc55ad42ef964a547059110573a177331cf4d' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index 9b1bf121b..d57ddb02d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -84,7 +84,7 @@ public class DescriptionFragment extends BaseFragment { private void setupDescription() { final Description description = streamInfo.getDescription(); if (description == null || isEmpty(description.getContent()) - || description == Description.emptyDescription) { + || description == Description.EMPTY_DESCRIPTION) { binding.detailDescriptionView.setVisibility(View.GONE); binding.detailSelectDescriptionButton.setVisibility(View.GONE); return; diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index dfc053a62..5557efd9c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -193,7 +193,7 @@ public class PeertubeInstanceListFragment extends Fragment { .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.yes, (dialog, which) -> { sharedPreferences.edit().remove(savedInstanceListKey).apply(); - selectInstance(PeertubeInstance.defaultInstance); + selectInstance(PeertubeInstance.DEFAULT_INSTANCE); updateInstanceList(); instanceListAdapter.notifyDataSetChanged(); }) diff --git a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java index 5b2858aa2..90886b63c 100644 --- a/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadMissionRecover.java @@ -132,7 +132,7 @@ public class DownloadMissionRecover extends Thread { switch (mRecovery.getKind()) { case 'a': for (AudioStream audio : mExtractor.getAudioStreams()) { - if (audio.average_bitrate == mRecovery.getDesiredBitrate() && audio.getFormat() == mRecovery.getFormat()) { + if (audio.getAverageBitrate() == mRecovery.getDesiredBitrate() && audio.getFormat() == mRecovery.getFormat()) { url = audio.getUrl(); break; } diff --git a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt index 403eee0c7..bba6fdd7f 100644 --- a/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt +++ b/app/src/main/java/us/shandian/giga/get/MissionRecoveryInfo.kt @@ -21,7 +21,7 @@ class MissionRecoveryInfo( constructor(stream: Stream) : this(format = stream.getFormat()!!) { when (stream) { is AudioStream -> { - desiredBitrate = stream.average_bitrate + desiredBitrate = stream.averageBitrate isDesired2 = false kind = 'a' } From ffa7efedd6b263d5ef85ce83d8334bacfc026b7d Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 15 Apr 2022 10:57:55 +0200 Subject: [PATCH 048/992] Hotfix release 0.22.2 (985) --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d8467d1e0..5d3b9e7e4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { resValue "string", "app_name", "NewPipe" minSdk 19 targetSdk 29 - versionCode 984 - versionName "0.22.1" + versionCode 985 + versionName "0.22.2" multiDexEnabled true From 5ce5f84f4a7fdb8c637175cbaefa527a16ead640 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 15 Apr 2022 11:05:19 +0200 Subject: [PATCH 049/992] Add hotfix changelogs for NewPipe 0.22.2 (985) The changelogs were just copied from 982.txt for all languages that had it, since the subject of the hotfix release was the same, i.e. youtube breaking --- fastlane/metadata/android/de/changelogs/985.txt | 1 + fastlane/metadata/android/en-US/changelogs/985.txt | 1 + fastlane/metadata/android/he/changelogs/985.txt | 1 + fastlane/metadata/android/it/changelogs/985.txt | 1 + fastlane/metadata/android/nl/changelogs/985.txt | 1 + fastlane/metadata/android/sv/changelogs/985.txt | 1 + fastlane/metadata/android/uk/changelogs/985.txt | 1 + fastlane/metadata/android/zh-Hans/changelogs/985.txt | 1 + 8 files changed, 8 insertions(+) create mode 100644 fastlane/metadata/android/de/changelogs/985.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/985.txt create mode 100644 fastlane/metadata/android/he/changelogs/985.txt create mode 100644 fastlane/metadata/android/it/changelogs/985.txt create mode 100644 fastlane/metadata/android/nl/changelogs/985.txt create mode 100644 fastlane/metadata/android/sv/changelogs/985.txt create mode 100644 fastlane/metadata/android/uk/changelogs/985.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/985.txt diff --git a/fastlane/metadata/android/de/changelogs/985.txt b/fastlane/metadata/android/de/changelogs/985.txt new file mode 100644 index 000000000..e2a10be2e --- /dev/null +++ b/fastlane/metadata/android/de/changelogs/985.txt @@ -0,0 +1 @@ +Behoben, dass YouTube keinen Stream abspielte. diff --git a/fastlane/metadata/android/en-US/changelogs/985.txt b/fastlane/metadata/android/en-US/changelogs/985.txt new file mode 100644 index 000000000..c720218ac --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/985.txt @@ -0,0 +1 @@ +Fixed YouTube not playing any stream \ No newline at end of file diff --git a/fastlane/metadata/android/he/changelogs/985.txt b/fastlane/metadata/android/he/changelogs/985.txt new file mode 100644 index 000000000..8123c9da3 --- /dev/null +++ b/fastlane/metadata/android/he/changelogs/985.txt @@ -0,0 +1 @@ +תוקנה התקלה ש־YouTube לא מנגן אף תזרים. diff --git a/fastlane/metadata/android/it/changelogs/985.txt b/fastlane/metadata/android/it/changelogs/985.txt new file mode 100644 index 000000000..296db3632 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/985.txt @@ -0,0 +1 @@ +Sistemato un problema nell'estrattore di YouTube che impediva di guardare qualsiasi video. \ No newline at end of file diff --git a/fastlane/metadata/android/nl/changelogs/985.txt b/fastlane/metadata/android/nl/changelogs/985.txt new file mode 100644 index 000000000..deaa12d0c --- /dev/null +++ b/fastlane/metadata/android/nl/changelogs/985.txt @@ -0,0 +1 @@ +Opgelost: YouTube speelt geen stream af. diff --git a/fastlane/metadata/android/sv/changelogs/985.txt b/fastlane/metadata/android/sv/changelogs/985.txt new file mode 100644 index 000000000..4c434af54 --- /dev/null +++ b/fastlane/metadata/android/sv/changelogs/985.txt @@ -0,0 +1 @@ +Fixade att YouTube inte spelade någon stream. diff --git a/fastlane/metadata/android/uk/changelogs/985.txt b/fastlane/metadata/android/uk/changelogs/985.txt new file mode 100644 index 000000000..41d4d11ce --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/985.txt @@ -0,0 +1 @@ +Виправлено проблему невідтворюваності трансляцій YouTube. diff --git a/fastlane/metadata/android/zh-Hans/changelogs/985.txt b/fastlane/metadata/android/zh-Hans/changelogs/985.txt new file mode 100644 index 000000000..8a5424c9e --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/985.txt @@ -0,0 +1 @@ +修复YouTube无法播放任何视频 From bc53bc7cfd49d522c037f237d0dbe01ec997a217 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 15 Apr 2022 13:36:11 +0200 Subject: [PATCH 050/992] Update NewPipeExtractor again to fix throttling on age restricted videos --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 5d3b9e7e4..f954d88e5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -189,7 +189,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:bebdc55ad42ef964a547059110573a177331cf4d' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:b77c72fb8826c3ffca0be5f96b066cca0a07b1c9' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" From 509036f162dcd00f2e5ba57ee79382f93801964c Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 17:50:10 +0200 Subject: [PATCH 051/992] Add changelog for 0.23.0 --- .../metadata/android/en-US/changelogs/986.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/986.txt diff --git a/fastlane/metadata/android/en-US/changelogs/986.txt b/fastlane/metadata/android/en-US/changelogs/986.txt new file mode 100644 index 000000000..fb88af966 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/986.txt @@ -0,0 +1,16 @@ +New +• Notifications for new streams +• Seamless transition between background and video players +• Change pitch by semitones +• Append the main player queue to a playlist + +Improved +• Remember speed/pitch step size +• Mitigate initial long buffering in the video player +• Improve player UI for Android TV +• Confirm before deleting all downloaded files + +Fixed +• Fix media button not hiding player controls +• Fix playback reset on player type change +• Fix rotating the playlist dialog \ No newline at end of file From 0a2fc0870629fa166543b41e4da3df8dea11f445 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 18:28:23 +0200 Subject: [PATCH 052/992] Release v0.23.0 (986) --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 049d04bd2..e93b2aaf4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { resValue "string", "app_name", "NewPipe" minSdk 19 targetSdk 29 - versionCode 985 - versionName "0.22.2" + versionCode 986 + versionName "0.23.0" multiDexEnabled true From f2fc2cc24af9900db8ad1cdf59cedf4ad34f6a26 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 18:57:02 +0200 Subject: [PATCH 053/992] Check whether to enable New streams settings in onCreate to prevent flickering --- .../newpipe/settings/NotificationsSettingsFragment.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt index e823c2fcf..14f74088c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -26,6 +26,10 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.notifications_settings) + + // main check is done in onResume, but also do it here to prevent flickering + preferenceScreen.isEnabled = + NotificationHelper.areNotificationsEnabledOnDevice(requireContext()) } override fun onStart() { @@ -64,7 +68,7 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen // If they are disabled, show a snackbar informing the user about that // while allowing them to open the device's app settings. val enabled = NotificationHelper.areNotificationsEnabledOnDevice(requireContext()) - preferenceScreen.isEnabled = enabled + preferenceScreen.isEnabled = enabled // it is disabled by default, see the xml if (!enabled) { if (notificationWarningSnackbar == null) { notificationWarningSnackbar = Snackbar.make( From fa58a818529f31c4a0b0567575030a936c05e7b8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 18:58:19 +0200 Subject: [PATCH 054/992] Fix New streams settings snackbar not being hidden on exiting --- .../newpipe/settings/NotificationsSettingsFragment.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt index 14f74088c..fcc9abf73 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationsSettingsFragment.kt @@ -89,9 +89,6 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen show() } } - } else { - notificationWarningSnackbar?.dismiss() - notificationWarningSnackbar = null } // (Re-)Create loader @@ -106,6 +103,9 @@ class NotificationsSettingsFragment : BasePreferenceFragment(), OnSharedPreferen loader?.dispose() loader = null + notificationWarningSnackbar?.dismiss() + notificationWarningSnackbar = null + super.onPause() } From 652d50173e64efd01eeca1c9454d205c2c513b4a Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sat, 1 Jan 2022 19:02:03 +0100 Subject: [PATCH 055/992] Major refactoring of PlaybackParameterDialog * Removed/Renamed methods * Use ``IcePick`` * Better structuring * Keep skipSilence when rotating the device (PlayQueueActivity only) --- .../helper/PlaybackParameterDialog.java | 726 ++++++------------ 1 file changed, 224 insertions(+), 502 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 1a55c21c3..b72b7ea7b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -9,10 +9,10 @@ import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.CheckBox; -import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; +import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -20,104 +20,88 @@ import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; -import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; +import java.util.Objects; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleFunction; + +import icepick.Icepick; +import icepick.State; + public class PlaybackParameterDialog extends DialogFragment { + private static final String TAG = "PlaybackParameterDialog"; + // Minimum allowable range in ExoPlayer private static final double MINIMUM_PLAYBACK_VALUE = 0.10f; private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; - private static final char STEP_UP_SIGN = '+'; - private static final char STEP_DOWN_SIGN = '-'; - - private static final double STEP_ONE_PERCENT_VALUE = 0.01f; - private static final double STEP_FIVE_PERCENT_VALUE = 0.05f; - private static final double STEP_TEN_PERCENT_VALUE = 0.10f; - private static final double STEP_TWENTY_FIVE_PERCENT_VALUE = 0.25f; - private static final double STEP_ONE_HUNDRED_PERCENT_VALUE = 1.00f; + private static final double STEP_1_PERCENT_VALUE = 0.01f; + private static final double STEP_5_PERCENT_VALUE = 0.05f; + private static final double STEP_10_PERCENT_VALUE = 0.10f; + private static final double STEP_25_PERCENT_VALUE = 0.25f; + private static final double STEP_100_PERCENT_VALUE = 1.00f; private static final double DEFAULT_TEMPO = 1.00f; private static final double DEFAULT_PITCH = 1.00f; - private static final int DEFAULT_SEMITONES = 0; - private static final double DEFAULT_STEP = STEP_TWENTY_FIVE_PERCENT_VALUE; + private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE; private static final boolean DEFAULT_SKIP_SILENCE = false; - @NonNull - private static final String TAG = "PlaybackParameterDialog"; - @NonNull - private static final String INITIAL_TEMPO_KEY = "initial_tempo_key"; - @NonNull - private static final String INITIAL_PITCH_KEY = "initial_pitch_key"; - - @NonNull - private static final String TEMPO_KEY = "tempo_key"; - @NonNull - private static final String PITCH_KEY = "pitch_key"; - @NonNull - private static final String STEP_SIZE_KEY = "step_size_key"; - - @NonNull - private final SliderStrategy strategy = new SliderStrategy.Quadratic( - MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE, - /*centerAt=*/1.00f, /*sliderGranularity=*/10000); + private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic( + MINIMUM_PLAYBACK_VALUE, + MAXIMUM_PLAYBACK_VALUE, + 1.00f, + 10_000); @Nullable private Callback callback; - private double initialTempo = DEFAULT_TEMPO; - private double initialPitch = DEFAULT_PITCH; - private int initialSemitones = DEFAULT_SEMITONES; - private boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; - private double tempo = DEFAULT_TEMPO; - private double pitch = DEFAULT_PITCH; - private int semitones = DEFAULT_SEMITONES; + @State + double initialTempo = DEFAULT_TEMPO; + @State + double initialPitch = DEFAULT_PITCH; + @State + boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; + + @State + double tempo = DEFAULT_TEMPO; + @State + double pitch = DEFAULT_PITCH; + @State + double stepSize = DEFAULT_STEP; + @State + boolean skipSilence = DEFAULT_SKIP_SILENCE; - @Nullable private SeekBar tempoSlider; - @Nullable private TextView tempoCurrentText; - @Nullable private TextView tempoStepDownText; - @Nullable private TextView tempoStepUpText; - @Nullable - private SeekBar pitchSlider; - @Nullable - private TextView pitchCurrentText; - @Nullable - private TextView pitchStepDownText; - @Nullable - private TextView pitchStepUpText; - @Nullable - private SeekBar semitoneSlider; - @Nullable - private TextView semitoneCurrentText; - @Nullable - private TextView semitoneStepDownText; - @Nullable - private TextView semitoneStepUpText; - @Nullable - private CheckBox unhookingCheckbox; - @Nullable - private CheckBox skipSilenceCheckbox; - @Nullable - private CheckBox adjustBySemitonesCheckbox; - public static PlaybackParameterDialog newInstance(final double playbackTempo, - final double playbackPitch, - final boolean playbackSkipSilence, - final Callback callback) { + private SeekBar pitchSlider; + private TextView pitchCurrentText; + private TextView pitchStepDownText; + private TextView pitchStepUpText; + + private CheckBox unhookingCheckbox; + private CheckBox skipSilenceCheckbox; + + public static PlaybackParameterDialog newInstance( + final double playbackTempo, + final double playbackPitch, + final boolean playbackSkipSilence, + final Callback callback + ) { final PlaybackParameterDialog dialog = new PlaybackParameterDialog(); dialog.callback = callback; + dialog.initialTempo = playbackTempo; dialog.initialPitch = playbackPitch; - - dialog.tempo = playbackTempo; - dialog.pitch = playbackPitch; - dialog.semitones = dialog.percentToSemitones(playbackPitch); - dialog.initialSkipSilence = playbackSkipSilence; + + dialog.tempo = dialog.initialTempo; + dialog.pitch = dialog.initialPitch; + dialog.skipSilence = dialog.initialSkipSilence; + return dialog; } @@ -126,7 +110,7 @@ public class PlaybackParameterDialog extends DialogFragment { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAttach(@NonNull final Context context) { + public void onAttach(final Context context) { super.onAttach(context); if (context instanceof Callback) { callback = (Callback) context; @@ -136,28 +120,9 @@ public class PlaybackParameterDialog extends DialogFragment { } @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - assureCorrectAppLanguage(getContext()); - super.onCreate(savedInstanceState); - if (savedInstanceState != null) { - initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO); - initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH); - initialSemitones = percentToSemitones(initialPitch); - - tempo = savedInstanceState.getDouble(TEMPO_KEY, DEFAULT_TEMPO); - pitch = savedInstanceState.getDouble(PITCH_KEY, DEFAULT_PITCH); - semitones = percentToSemitones(pitch); - } - } - - @Override - public void onSaveInstanceState(@NonNull final Bundle outState) { + public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); - outState.putDouble(INITIAL_TEMPO_KEY, initialTempo); - outState.putDouble(INITIAL_PITCH_KEY, initialPitch); - - outState.putDouble(TEMPO_KEY, getCurrentTempo()); - outState.putDouble(PITCH_KEY, getCurrentPitch()); + Icepick.saveInstanceState(this, outState); } /*////////////////////////////////////////////////////////////////////////// @@ -168,20 +133,28 @@ public class PlaybackParameterDialog extends DialogFragment { @Override public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { assureCorrectAppLanguage(getContext()); + Icepick.restoreInstanceState(this, savedInstanceState); + final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null); - setupControlViews(view); + initUI(view); + initUIData(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) .setView(view) .setCancelable(true) - .setNegativeButton(R.string.cancel, (dialogInterface, i) -> - setPlaybackParameters(initialTempo, initialPitch, - initialSemitones, initialSkipSilence)) - .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> - setPlaybackParameters(DEFAULT_TEMPO, DEFAULT_PITCH, - DEFAULT_SEMITONES, DEFAULT_SKIP_SILENCE)) - .setPositiveButton(R.string.ok, (dialogInterface, i) -> - setCurrentPlaybackParameters()); + .setNegativeButton(R.string.cancel, (dialogInterface, i) -> { + setAndUpdateTempo(initialTempo); + setAndUpdatePitch(initialPitch); + setAndUpdateSkipSilence(initialSkipSilence); + updateCallback(); + }) + .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> { + setAndUpdateTempo(DEFAULT_TEMPO); + setAndUpdatePitch(DEFAULT_PITCH); + setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE); + updateCallback(); + }) + .setPositiveButton(R.string.ok, (dialogInterface, i) -> updateCallback()); return dialogBuilder.create(); } @@ -190,353 +163,171 @@ public class PlaybackParameterDialog extends DialogFragment { // Control Views //////////////////////////////////////////////////////////////////////////*/ - private void setupControlViews(@NonNull final View rootView) { - setupHookingControl(rootView); - setupSkipSilenceControl(rootView); - setupAdjustBySemitonesControl(rootView); + private void initUI(@NonNull final View rootView) { + // Tempo + tempoSlider = Objects.requireNonNull(rootView.findViewById(R.id.tempoSeekbar)); + tempoCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.tempoCurrentText)); + tempoStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepUp)); + tempoStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepDown)); - setupTempoControl(rootView); - setupPitchControl(rootView); - setupSemitoneControl(rootView); + setText(rootView, R.id.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); + setText(rootView, R.id.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); - togglePitchSliderType(rootView); + // Pitch + pitchSlider = Objects.requireNonNull(rootView.findViewById(R.id.pitchSeekbar)); + pitchCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.pitchCurrentText)); + pitchStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepUp)); + pitchStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepDown)); - setupStepSizeSelector(rootView); + setText(rootView, R.id.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); + setText(rootView, R.id.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); + + // Steps + setupStepTextView(rootView, R.id.stepSizeOnePercent, STEP_1_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeFivePercent, STEP_5_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeTenPercent, STEP_10_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); + setupStepTextView(rootView, R.id.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); + + // Bottom controls + unhookingCheckbox = + Objects.requireNonNull(rootView.findViewById(R.id.unhookCheckbox)); + skipSilenceCheckbox = + Objects.requireNonNull(rootView.findViewById(R.id.skipSilenceCheckbox)); } - private void togglePitchSliderType(@NonNull final View rootView) { - final RelativeLayout pitchControl = rootView.findViewById(R.id.pitchControl); - final RelativeLayout semitoneControl = rootView.findViewById(R.id.semitoneControl); - - final View separatorStepSizeSelector = - rootView.findViewById(R.id.separatorStepSizeSelector); - final RelativeLayout.LayoutParams params = - (RelativeLayout.LayoutParams) separatorStepSizeSelector.getLayoutParams(); - if (pitchControl != null && semitoneControl != null && unhookingCheckbox != null) { - if (getCurrentAdjustBySemitones()) { - // replaces pitchControl slider with semitoneControl slider - pitchControl.setVisibility(View.GONE); - semitoneControl.setVisibility(View.VISIBLE); - params.addRule(RelativeLayout.BELOW, R.id.semitoneControl); - - // forces unhook for semitones - unhookingCheckbox.setChecked(true); - unhookingCheckbox.setEnabled(false); - - setupTempoStepSizeSelector(rootView); - } else { - semitoneControl.setVisibility(View.GONE); - pitchControl.setVisibility(View.VISIBLE); - params.addRule(RelativeLayout.BELOW, R.id.pitchControl); - - // (re)enables hooking selection - unhookingCheckbox.setEnabled(true); - setupCombinedStepSizeSelector(rootView); - } - } + private TextView setText( + final TextView textView, + final DoubleFunction formatter, + final double value + ) { + Objects.requireNonNull(textView).setText(formatter.apply(value)); + return textView; } - private void setupTempoControl(@NonNull final View rootView) { - tempoSlider = rootView.findViewById(R.id.tempoSeekbar); - final TextView tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText); - final TextView tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText); - tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText); - tempoStepUpText = rootView.findViewById(R.id.tempoStepUp); - tempoStepDownText = rootView.findViewById(R.id.tempoStepDown); - - if (tempoCurrentText != null) { - tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo)); - } - if (tempoMaximumText != null) { - tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE)); - } - if (tempoMinimumText != null) { - tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE)); - } - - if (tempoSlider != null) { - tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); - tempoSlider.setProgress(strategy.progressOf(tempo)); - tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener()); - } + private TextView setText( + final View rootView, + @IdRes final int idRes, + final DoubleFunction formatter, + final double value + ) { + final TextView textView = rootView.findViewById(idRes); + setText(textView, formatter, value); + return textView; } - private void setupPitchControl(@NonNull final View rootView) { - pitchSlider = rootView.findViewById(R.id.pitchSeekbar); - final TextView pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText); - final TextView pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText); - pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText); - pitchStepDownText = rootView.findViewById(R.id.pitchStepDown); - pitchStepUpText = rootView.findViewById(R.id.pitchStepUp); - - if (pitchCurrentText != null) { - pitchCurrentText.setText(PlayerHelper.formatPitch(pitch)); - } - if (pitchMaximumText != null) { - pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE)); - } - if (pitchMinimumText != null) { - pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE)); - } - - if (pitchSlider != null) { - pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); - pitchSlider.setProgress(strategy.progressOf(pitch)); - pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener()); - } + private void setupStepTextView( + final View rootView, + @IdRes final int idRes, + final double stepSizeValue + ) { + setText(rootView, idRes, PlaybackParameterDialog::getPercentString, stepSizeValue) + .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); } - private void setupSemitoneControl(@NonNull final View rootView) { - semitoneSlider = rootView.findViewById(R.id.semitoneSeekbar); - semitoneCurrentText = rootView.findViewById(R.id.semitoneCurrentText); - semitoneStepDownText = rootView.findViewById(R.id.semitoneStepDown); - semitoneStepUpText = rootView.findViewById(R.id.semitoneStepUp); + private void initUIData() { + // Tempo + tempoSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + setAndUpdateTempo(tempo); + tempoSlider.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated)); - if (semitoneCurrentText != null) { - semitoneCurrentText.setText(getSignedSemitonesString(semitones)); - } + registerOnStepClickListener( + tempoStepDownText, tempo, -1, this::onTempoSliderUpdated); + registerOnStepClickListener( + tempoStepUpText, tempo, 1, this::onTempoSliderUpdated); - if (semitoneSlider != null) { - setSemitoneSlider(semitones); - semitoneSlider.setOnSeekBarChangeListener(getOnSemitoneChangedListener()); - } + // Pitch + pitchSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + setAndUpdatePitch(pitch); + pitchSlider.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated)); - } + registerOnStepClickListener( + pitchStepDownText, pitch, -1, this::onPitchSliderUpdated); + registerOnStepClickListener( + pitchStepUpText, pitch, 1, this::onPitchSliderUpdated); - private void setupHookingControl(@NonNull final View rootView) { - unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); - if (unhookingCheckbox != null) { - // restores whether pitch and tempo are unhooked or not - unhookingCheckbox.setChecked(PreferenceManager - .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_unhook_key), true)); + // Steps + setAndUpdateStepSize(stepSize); - unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - // saves whether pitch and tempo are unhooked or not - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .edit() - .putBoolean(getString(R.string.playback_unhook_key), isChecked) - .apply(); - - if (!isChecked) { - // when unchecked, slides back to the minimum of current tempo or pitch - final double minimum = Math.min(getCurrentPitch(), getCurrentTempo()); - setSliders(minimum); - setCurrentPlaybackParameters(); - } - }); - } - } - - private void setupSkipSilenceControl(@NonNull final View rootView) { - skipSilenceCheckbox = rootView.findViewById(R.id.skipSilenceCheckbox); - if (skipSilenceCheckbox != null) { - skipSilenceCheckbox.setChecked(initialSkipSilence); - skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> - setCurrentPlaybackParameters()); - } - } - - private void setupAdjustBySemitonesControl(@NonNull final View rootView) { - adjustBySemitonesCheckbox = rootView.findViewById(R.id.adjustBySemitonesCheckbox); - if (adjustBySemitonesCheckbox != null) { - // restores whether semitone adjustment is used or not - adjustBySemitonesCheckbox.setChecked(PreferenceManager + // Bottom controls + // restore whether pitch and tempo are unhooked or not + unhookingCheckbox.setChecked(PreferenceManager .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_adjust_by_semitones_key), true)); + .getBoolean(getString(R.string.playback_unhook_key), true)); - // stores whether semitone adjustment is used or not - adjustBySemitonesCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - PreferenceManager.getDefaultSharedPreferences(requireContext()) + unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + // save whether pitch and tempo are unhooked or not + PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() - .putBoolean(getString(R.string.playback_adjust_by_semitones_key), isChecked) + .putBoolean(getString(R.string.playback_unhook_key), isChecked) .apply(); - togglePitchSliderType(rootView); - if (isChecked) { - setPlaybackParameters( - getCurrentTempo(), - getCurrentPitch(), - Integer.min(12, - Integer.max(-12, percentToSemitones(getCurrentPitch()) - )), - getCurrentSkipSilence() - ); - setSemitoneSlider(Integer.min(12, - Integer.max(-12, percentToSemitones(getCurrentPitch())) - )); - } else { - setPlaybackParameters( - getCurrentTempo(), - semitonesToPercent(getCurrentSemitones()), - getCurrentSemitones(), - getCurrentSkipSilence() - ); - setPitchSlider(semitonesToPercent(getCurrentSemitones())); - } - }); - } + + if (!isChecked) { + // when unchecked, slide back to the minimum of current tempo or pitch + setSliders(Math.min(pitch, tempo)); + } + }); + + setAndUpdateSkipSilence(skipSilence); + skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + skipSilence = isChecked; + updateCallback(); + }); } - private void setupStepSizeSelector(@NonNull final View rootView) { - setStepSize(PreferenceManager - .getDefaultSharedPreferences(requireContext()) - .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP)); - - final TextView stepSizeOnePercentText = rootView.findViewById(R.id.stepSizeOnePercent); - final TextView stepSizeFivePercentText = rootView.findViewById(R.id.stepSizeFivePercent); - final TextView stepSizeTenPercentText = rootView.findViewById(R.id.stepSizeTenPercent); - final TextView stepSizeTwentyFivePercentText = rootView - .findViewById(R.id.stepSizeTwentyFivePercent); - final TextView stepSizeOneHundredPercentText = rootView - .findViewById(R.id.stepSizeOneHundredPercent); - - if (stepSizeOnePercentText != null) { - stepSizeOnePercentText.setText(getPercentString(STEP_ONE_PERCENT_VALUE)); - stepSizeOnePercentText - .setOnClickListener(view -> setStepSize(STEP_ONE_PERCENT_VALUE)); - } - - if (stepSizeFivePercentText != null) { - stepSizeFivePercentText.setText(getPercentString(STEP_FIVE_PERCENT_VALUE)); - stepSizeFivePercentText - .setOnClickListener(view -> setStepSize(STEP_FIVE_PERCENT_VALUE)); - } - - if (stepSizeTenPercentText != null) { - stepSizeTenPercentText.setText(getPercentString(STEP_TEN_PERCENT_VALUE)); - stepSizeTenPercentText - .setOnClickListener(view -> setStepSize(STEP_TEN_PERCENT_VALUE)); - } - - if (stepSizeTwentyFivePercentText != null) { - stepSizeTwentyFivePercentText - .setText(getPercentString(STEP_TWENTY_FIVE_PERCENT_VALUE)); - stepSizeTwentyFivePercentText - .setOnClickListener(view -> setStepSize(STEP_TWENTY_FIVE_PERCENT_VALUE)); - } - - if (stepSizeOneHundredPercentText != null) { - stepSizeOneHundredPercentText - .setText(getPercentString(STEP_ONE_HUNDRED_PERCENT_VALUE)); - stepSizeOneHundredPercentText - .setOnClickListener(view -> setStepSize(STEP_ONE_HUNDRED_PERCENT_VALUE)); - } + private void registerOnStepClickListener( + final TextView stepTextView, + final double currentValue, + final double direction, // -1 for step down, +1 for step up + final DoubleConsumer newValueConsumer + ) { + stepTextView.setOnClickListener(view -> + newValueConsumer.accept(currentValue * direction) + ); } - private void setupTempoStepSizeSelector(@NonNull final View rootView) { - final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); - if (playbackStepTypeText != null) { - playbackStepTypeText.setText(R.string.playback_tempo_step); - } - setupStepSizeSelector(rootView); + private void setAndUpdateStepSize(final double newStepSize) { + this.stepSize = newStepSize; + + tempoStepUpText.setText(getStepUpPercentString(newStepSize)); + tempoStepDownText.setText(getStepDownPercentString(newStepSize)); + + pitchStepUpText.setText(getStepUpPercentString(newStepSize)); + pitchStepDownText.setText(getStepDownPercentString(newStepSize)); } - private void setupCombinedStepSizeSelector(@NonNull final View rootView) { - final TextView playbackStepTypeText = rootView.findViewById(R.id.playback_step_type); - if (playbackStepTypeText != null) { - playbackStepTypeText.setText(R.string.playback_step); - } - setupStepSizeSelector(rootView); - } - - private void setStepSize(final double stepSize) { - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .edit() - .putFloat(getString(R.string.adjustment_step_key), (float) stepSize) - .apply(); - - if (tempoStepUpText != null) { - tempoStepUpText.setText(getStepUpPercentString(stepSize)); - tempoStepUpText.setOnClickListener(view -> { - onTempoSliderUpdated(getCurrentTempo() + stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (tempoStepDownText != null) { - tempoStepDownText.setText(getStepDownPercentString(stepSize)); - tempoStepDownText.setOnClickListener(view -> { - onTempoSliderUpdated(getCurrentTempo() - stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (pitchStepUpText != null) { - pitchStepUpText.setText(getStepUpPercentString(stepSize)); - pitchStepUpText.setOnClickListener(view -> { - onPitchSliderUpdated(getCurrentPitch() + stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (pitchStepDownText != null) { - pitchStepDownText.setText(getStepDownPercentString(stepSize)); - pitchStepDownText.setOnClickListener(view -> { - onPitchSliderUpdated(getCurrentPitch() - stepSize); - setCurrentPlaybackParameters(); - }); - } - - if (semitoneStepDownText != null) { - semitoneStepDownText.setOnClickListener(view -> { - onSemitoneSliderUpdated(getCurrentSemitones() - 1); - setCurrentPlaybackParameters(); - }); - } - - if (semitoneStepUpText != null) { - semitoneStepUpText.setOnClickListener(view -> { - onSemitoneSliderUpdated(getCurrentSemitones() + 1); - setCurrentPlaybackParameters(); - }); - } + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { + this.skipSilence = newSkipSilence; + skipSilenceCheckbox.setChecked(newSkipSilence); } /*////////////////////////////////////////////////////////////////////////// // Sliders //////////////////////////////////////////////////////////////////////////*/ - private SimpleOnSeekBarChangeListener getOnTempoChangedListener() { - return new SimpleOnSeekBarChangeListener() { + private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener( + final DoubleConsumer newValueConsumer + ) { + return new SeekBar.OnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, + public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { - final double currentTempo = strategy.valueOf(progress); - if (fromUser) { - onTempoSliderUpdated(currentTempo); - setCurrentPlaybackParameters(); + if (fromUser) { // this change is first in chain + newValueConsumer.accept(QUADRATIC_STRATEGY.valueOf(progress)); + updateCallback(); } } - }; - } - private SimpleOnSeekBarChangeListener getOnPitchChangedListener() { - return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, - final boolean fromUser) { - final double currentPitch = strategy.valueOf(progress); - if (fromUser) { // this change is first in chain - onPitchSliderUpdated(currentPitch); - setCurrentPlaybackParameters(); - } + public void onStartTrackingTouch(final SeekBar seekBar) { + // Do nothing } - }; - } - private SimpleOnSeekBarChangeListener getOnSemitoneChangedListener() { - return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, - final boolean fromUser) { - // semitone slider supplies values 0 to 24, subtraction by 12 is required - final int currentSemitones = progress - 12; - if (fromUser) { // this change is first in chain - onSemitoneSliderUpdated(currentSemitones); - // line below also saves semitones as pitch percentages - onPitchSliderUpdated(semitonesToPercent(currentSemitones)); - setCurrentPlaybackParameters(); - } + public void onStopTrackingTouch(final SeekBar seekBar) { + // Do nothing } }; } @@ -545,7 +336,7 @@ public class PlaybackParameterDialog extends DialogFragment { if (!unhookingCheckbox.isChecked()) { setSliders(newTempo); } else { - setTempoSlider(newTempo); + setAndUpdateTempo(newTempo); } } @@ -553,109 +344,53 @@ public class PlaybackParameterDialog extends DialogFragment { if (!unhookingCheckbox.isChecked()) { setSliders(newPitch); } else { - setPitchSlider(newPitch); + setAndUpdatePitch(newPitch); } } - private void onSemitoneSliderUpdated(final int newSemitone) { - setSemitoneSlider(newSemitone); - } - private void setSliders(final double newValue) { - setTempoSlider(newValue); - setPitchSlider(newValue); + setAndUpdateTempo(newValue); + setAndUpdatePitch(newValue); } - private void setTempoSlider(final double newTempo) { - if (tempoSlider == null) { - return; - } - tempoSlider.setProgress(strategy.progressOf(newTempo)); + private void setAndUpdateTempo(final double newTempo) { + this.tempo = newTempo; + tempoSlider.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); + setText(tempoCurrentText, PlayerHelper::formatSpeed, tempo); } - private void setPitchSlider(final double newPitch) { - if (pitchSlider == null) { - return; - } - pitchSlider.setProgress(strategy.progressOf(newPitch)); - } - - private void setSemitoneSlider(final int newSemitone) { - if (semitoneSlider == null) { - return; - } - semitoneSlider.setProgress(newSemitone + 12); + private void setAndUpdatePitch(final double newPitch) { + this.pitch = newPitch; + pitchSlider.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); + setText(pitchCurrentText, PlayerHelper::formatPitch, pitch); } /*////////////////////////////////////////////////////////////////////////// // Helper //////////////////////////////////////////////////////////////////////////*/ - private void setCurrentPlaybackParameters() { - if (getCurrentAdjustBySemitones()) { - setPlaybackParameters( - getCurrentTempo(), - semitonesToPercent(getCurrentSemitones()), - getCurrentSemitones(), - getCurrentSkipSilence() - ); - } else { - setPlaybackParameters( - getCurrentTempo(), - getCurrentPitch(), - percentToSemitones(getCurrentPitch()), - getCurrentSkipSilence() + private void updateCallback() { + if (callback == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "Updating callback: " + + "tempo = [" + tempo + "], " + + "pitch = [" + pitch + "], " + + "skipSilence = [" + skipSilence + "]" ); } - } - - private void setPlaybackParameters(final double newTempo, final double newPitch, - final int newSemitones, final boolean skipSilence) { - if (callback != null && tempoCurrentText != null - && pitchCurrentText != null && semitoneCurrentText != null) { - if (DEBUG) { - Log.d(TAG, "Setting playback parameters to " - + "tempo=[" + newTempo + "], " - + "pitch=[" + newPitch + "], " - + "semitones=[" + newSemitones + "]"); - } - - tempoCurrentText.setText(PlayerHelper.formatSpeed(newTempo)); - pitchCurrentText.setText(PlayerHelper.formatPitch(newPitch)); - semitoneCurrentText.setText(getSignedSemitonesString(newSemitones)); - callback.onPlaybackParameterChanged((float) newTempo, (float) newPitch, skipSilence); - } - } - - private double getCurrentTempo() { - return tempoSlider == null ? tempo : strategy.valueOf(tempoSlider.getProgress()); - } - - private double getCurrentPitch() { - return pitchSlider == null ? pitch : strategy.valueOf(pitchSlider.getProgress()); - } - - private int getCurrentSemitones() { - // semitoneSlider is absolute, that's why - 12 - return semitoneSlider == null ? semitones : semitoneSlider.getProgress() - 12; - } - - private boolean getCurrentSkipSilence() { - return skipSilenceCheckbox != null && skipSilenceCheckbox.isChecked(); - } - - private boolean getCurrentAdjustBySemitones() { - return adjustBySemitonesCheckbox != null && adjustBySemitonesCheckbox.isChecked(); + callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence); } @NonNull private static String getStepUpPercentString(final double percent) { - return STEP_UP_SIGN + getPercentString(percent); + return '+' + getPercentString(percent); } @NonNull private static String getStepDownPercentString(final double percent) { - return STEP_DOWN_SIGN + getPercentString(percent); + return '-' + getPercentString(percent); } @NonNull @@ -663,21 +398,8 @@ public class PlaybackParameterDialog extends DialogFragment { return PlayerHelper.formatPitch(percent); } - @NonNull - private static String getSignedSemitonesString(final int semitones) { - return semitones > 0 ? "+" + semitones : "" + semitones; - } - public interface Callback { void onPlaybackParameterChanged(float playbackTempo, float playbackPitch, boolean playbackSkipSilence); } - - public double semitonesToPercent(final int inSemitones) { - return Math.pow(2, inSemitones / 12.0); - } - - public int percentToSemitones(final double inPercent) { - return (int) Math.round(12 * Math.log(inPercent) / Math.log(2)); - } } From 4cdf6eda2cf3d9073d9ccad7c339bfb1856189c2 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Mon, 28 Feb 2022 20:45:23 +0100 Subject: [PATCH 056/992] Use viewbinding --- .../helper/PlaybackParameterDialog.java | 124 ++++++------------ 1 file changed, 42 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index b72b7ea7b..e1874fec0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -7,12 +7,10 @@ import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.util.Log; -import android.view.View; -import android.widget.CheckBox; +import android.view.LayoutInflater; import android.widget.SeekBar; import android.widget.TextView; -import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -20,6 +18,7 @@ import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; import org.schabi.newpipe.util.SliderStrategy; import java.util.Objects; @@ -72,18 +71,7 @@ public class PlaybackParameterDialog extends DialogFragment { @State boolean skipSilence = DEFAULT_SKIP_SILENCE; - private SeekBar tempoSlider; - private TextView tempoCurrentText; - private TextView tempoStepDownText; - private TextView tempoStepUpText; - - private SeekBar pitchSlider; - private TextView pitchCurrentText; - private TextView pitchStepDownText; - private TextView pitchStepUpText; - - private CheckBox unhookingCheckbox; - private CheckBox skipSilenceCheckbox; + private DialogPlaybackParameterBinding binding; public static PlaybackParameterDialog newInstance( final double playbackTempo, @@ -110,7 +98,7 @@ public class PlaybackParameterDialog extends DialogFragment { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAttach(final Context context) { + public void onAttach(@NonNull final Context context) { super.onAttach(context); if (context instanceof Callback) { callback = (Callback) context; @@ -120,7 +108,7 @@ public class PlaybackParameterDialog extends DialogFragment { } @Override - public void onSaveInstanceState(final Bundle outState) { + public void onSaveInstanceState(@NonNull final Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } @@ -135,12 +123,12 @@ public class PlaybackParameterDialog extends DialogFragment { assureCorrectAppLanguage(getContext()); Icepick.restoreInstanceState(this, savedInstanceState); - final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null); - initUI(view); + binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext())); + initUI(); initUIData(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) - .setView(view) + .setView(binding.getRoot()) .setCancelable(true) .setNegativeButton(R.string.cancel, (dialogInterface, i) -> { setAndUpdateTempo(initialTempo); @@ -163,37 +151,21 @@ public class PlaybackParameterDialog extends DialogFragment { // Control Views //////////////////////////////////////////////////////////////////////////*/ - private void initUI(@NonNull final View rootView) { + private void initUI() { // Tempo - tempoSlider = Objects.requireNonNull(rootView.findViewById(R.id.tempoSeekbar)); - tempoCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.tempoCurrentText)); - tempoStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepUp)); - tempoStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.tempoStepDown)); - - setText(rootView, R.id.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); - setText(rootView, R.id.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); + setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); + setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); // Pitch - pitchSlider = Objects.requireNonNull(rootView.findViewById(R.id.pitchSeekbar)); - pitchCurrentText = Objects.requireNonNull(rootView.findViewById(R.id.pitchCurrentText)); - pitchStepUpText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepUp)); - pitchStepDownText = Objects.requireNonNull(rootView.findViewById(R.id.pitchStepDown)); - - setText(rootView, R.id.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); - setText(rootView, R.id.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); + setText(binding.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); + setText(binding.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); // Steps - setupStepTextView(rootView, R.id.stepSizeOnePercent, STEP_1_PERCENT_VALUE); - setupStepTextView(rootView, R.id.stepSizeFivePercent, STEP_5_PERCENT_VALUE); - setupStepTextView(rootView, R.id.stepSizeTenPercent, STEP_10_PERCENT_VALUE); - setupStepTextView(rootView, R.id.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); - setupStepTextView(rootView, R.id.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); - - // Bottom controls - unhookingCheckbox = - Objects.requireNonNull(rootView.findViewById(R.id.unhookCheckbox)); - skipSilenceCheckbox = - Objects.requireNonNull(rootView.findViewById(R.id.skipSilenceCheckbox)); + setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE); + setupStepTextView(binding.stepSizeFivePercent, STEP_5_PERCENT_VALUE); + setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE); + setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); + setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); } private TextView setText( @@ -205,59 +177,47 @@ public class PlaybackParameterDialog extends DialogFragment { return textView; } - private TextView setText( - final View rootView, - @IdRes final int idRes, - final DoubleFunction formatter, - final double value - ) { - final TextView textView = rootView.findViewById(idRes); - setText(textView, formatter, value); - return textView; - } - private void setupStepTextView( - final View rootView, - @IdRes final int idRes, + final TextView textView, final double stepSizeValue ) { - setText(rootView, idRes, PlaybackParameterDialog::getPercentString, stepSizeValue) + setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue) .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); } private void initUIData() { // Tempo - tempoSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); setAndUpdateTempo(tempo); - tempoSlider.setOnSeekBarChangeListener( + binding.tempoSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated)); registerOnStepClickListener( - tempoStepDownText, tempo, -1, this::onTempoSliderUpdated); + binding.tempoStepDown, tempo, -1, this::onTempoSliderUpdated); registerOnStepClickListener( - tempoStepUpText, tempo, 1, this::onTempoSliderUpdated); + binding.tempoStepUp, tempo, 1, this::onTempoSliderUpdated); // Pitch - pitchSlider.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); + binding.pitchSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); setAndUpdatePitch(pitch); - pitchSlider.setOnSeekBarChangeListener( + binding.pitchSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated)); registerOnStepClickListener( - pitchStepDownText, pitch, -1, this::onPitchSliderUpdated); + binding.pitchStepDown, pitch, -1, this::onPitchSliderUpdated); registerOnStepClickListener( - pitchStepUpText, pitch, 1, this::onPitchSliderUpdated); + binding.pitchStepUp, pitch, 1, this::onPitchSliderUpdated); // Steps setAndUpdateStepSize(stepSize); // Bottom controls // restore whether pitch and tempo are unhooked or not - unhookingCheckbox.setChecked(PreferenceManager + binding.unhookCheckbox.setChecked(PreferenceManager .getDefaultSharedPreferences(requireContext()) .getBoolean(getString(R.string.playback_unhook_key), true)); - unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + binding.unhookCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { // save whether pitch and tempo are unhooked or not PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() @@ -271,7 +231,7 @@ public class PlaybackParameterDialog extends DialogFragment { }); setAndUpdateSkipSilence(skipSilence); - skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { skipSilence = isChecked; updateCallback(); }); @@ -291,16 +251,16 @@ public class PlaybackParameterDialog extends DialogFragment { private void setAndUpdateStepSize(final double newStepSize) { this.stepSize = newStepSize; - tempoStepUpText.setText(getStepUpPercentString(newStepSize)); - tempoStepDownText.setText(getStepDownPercentString(newStepSize)); + binding.tempoStepUp.setText(getStepUpPercentString(newStepSize)); + binding.tempoStepDown.setText(getStepDownPercentString(newStepSize)); - pitchStepUpText.setText(getStepUpPercentString(newStepSize)); - pitchStepDownText.setText(getStepDownPercentString(newStepSize)); + binding.pitchStepUp.setText(getStepUpPercentString(newStepSize)); + binding.pitchStepDown.setText(getStepDownPercentString(newStepSize)); } private void setAndUpdateSkipSilence(final boolean newSkipSilence) { this.skipSilence = newSkipSilence; - skipSilenceCheckbox.setChecked(newSkipSilence); + binding.skipSilenceCheckbox.setChecked(newSkipSilence); } /*////////////////////////////////////////////////////////////////////////// @@ -333,7 +293,7 @@ public class PlaybackParameterDialog extends DialogFragment { } private void onTempoSliderUpdated(final double newTempo) { - if (!unhookingCheckbox.isChecked()) { + if (!binding.unhookCheckbox.isChecked()) { setSliders(newTempo); } else { setAndUpdateTempo(newTempo); @@ -341,7 +301,7 @@ public class PlaybackParameterDialog extends DialogFragment { } private void onPitchSliderUpdated(final double newPitch) { - if (!unhookingCheckbox.isChecked()) { + if (!binding.unhookCheckbox.isChecked()) { setSliders(newPitch); } else { setAndUpdatePitch(newPitch); @@ -355,14 +315,14 @@ public class PlaybackParameterDialog extends DialogFragment { private void setAndUpdateTempo(final double newTempo) { this.tempo = newTempo; - tempoSlider.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); - setText(tempoCurrentText, PlayerHelper::formatSpeed, tempo); + binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); + setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo); } private void setAndUpdatePitch(final double newPitch) { this.pitch = newPitch; - pitchSlider.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); - setText(pitchCurrentText, PlayerHelper::formatPitch, pitch); + binding.pitchSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); + setText(binding.pitchCurrentText, PlayerHelper::formatPitch, pitch); } /*////////////////////////////////////////////////////////////////////////// From 6e0c3804097a1e3749d329c82e8cca3c72d7ec18 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Mon, 28 Feb 2022 21:02:43 +0100 Subject: [PATCH 057/992] Remove redundant attributes --- .../res/layout/dialog_playback_parameter.xml | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 862b2ea67..27cf0dbd6 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -39,7 +39,6 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentStart="true" - android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:background="?attr/selectableItemBackground" android:clickable="true" @@ -57,9 +56,7 @@ android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_toStartOf="@id/tempoStepUp" - android:layout_toLeftOf="@id/tempoStepUp" android:layout_toEndOf="@id/tempoStepDown" - android:layout_toRightOf="@id/tempoStepDown" android:orientation="horizontal"> Date: Mon, 28 Feb 2022 21:04:24 +0100 Subject: [PATCH 058/992] Remove invalid parameters --- app/src/main/res/layout/dialog_playback_parameter.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 27cf0dbd6..87c26b831 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -463,8 +463,6 @@ android:id="@+id/adjustBySemitonesCheckbox" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_below="@id/skipSilenceCheckbox" - android:layout_centerHorizontal="true" android:checked="false" android:clickable="true" android:focusable="true" From a4c083e7f98c535993c9e22d9d3ae99d5c410a0c Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Tue, 1 Mar 2022 20:52:48 +0100 Subject: [PATCH 059/992] Rework dialog * De-Duplicated some fields * Use a container for the pitch controls * Name pitch related elements correctly --- .../res/layout/dialog_playback_parameter.xml | 310 +++++++++--------- 1 file changed, 157 insertions(+), 153 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 87c26b831..47394b724 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -29,7 +29,7 @@ @@ -146,203 +146,207 @@ android:textStyle="bold" /> - - + android:layout_marginTop="3dp"> - - + tools:text="-5%" /> + + + + + + + + + + + - - + tools:text="+5%" /> - - - - - - - - - - - - - + android:layout_height="match_parent" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" + android:layout_toStartOf="@+id/pitchSemitoneStepUp" + android:layout_toEndOf="@+id/pitchSemitoneStepDown" + android:orientation="horizontal"> - + + + + + + + + + + + Date: Tue, 1 Mar 2022 21:19:30 +0100 Subject: [PATCH 060/992] Shrunk dialog a bit --- app/src/main/res/layout/dialog_playback_parameter.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 47394b724..1ab9a95e9 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -356,7 +356,7 @@ From dae5aa38a83a892f43a3589a21bada854601273d Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 2 Mar 2022 20:58:14 +0100 Subject: [PATCH 061/992] Fine tuned dialog (no scrollers in fullscreen on 5in phone) --- .../res/layout/dialog_playback_parameter.xml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 1ab9a95e9..75269ffd3 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -31,7 +31,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/tempoControlText" - android:layout_marginTop="3dp" + android:layout_marginTop="1dp" android:orientation="horizontal"> @@ -129,9 +129,9 @@ android:layout_height="1dp" android:layout_below="@id/tempoControl" android:layout_marginStart="12dp" - android:layout_marginTop="6dp" - android:layout_marginEnd="6dp" - android:layout_marginBottom="6dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="12dp" + android:layout_marginBottom="4dp" android:background="?attr/separator_color" /> + android:layout_marginTop="1dp"> @@ -319,7 +319,7 @@ android:layout_height="wrap_content" android:layout_below="@+id/pitchSemitoneCurrentText" android:max="24" - android:paddingBottom="4dp" + android:paddingBottom="2dp" android:progress="12" /> @@ -348,9 +348,9 @@ android:layout_height="1dp" android:layout_below="@+id/pitchControlContainer" android:layout_marginStart="12dp" - android:layout_marginTop="6dp" + android:layout_marginTop="4dp" android:layout_marginEnd="12dp" - android:layout_marginBottom="6dp" + android:layout_marginBottom="4dp" android:background="?attr/separator_color" /> Date: Wed, 2 Mar 2022 21:00:19 +0100 Subject: [PATCH 062/992] Reworked/Implemented PlaybackParameterDialog functionallity * Add support for semitones * Fixed some minor bugs * Improved some methods --- .../helper/PlaybackParameterDialog.java | 320 +++++++++++++----- .../player/helper/PlayerSemitoneHelper.java | 37 ++ 2 files changed, 264 insertions(+), 93 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index e1874fec0..709216ece 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -8,11 +8,14 @@ import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; import android.widget.SeekBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; @@ -22,8 +25,10 @@ import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; import org.schabi.newpipe.util.SliderStrategy; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.DoubleConsumer; import java.util.function.DoubleFunction; +import java.util.function.DoubleSupplier; import icepick.Icepick; import icepick.State; @@ -32,8 +37,8 @@ public class PlaybackParameterDialog extends DialogFragment { private static final String TAG = "PlaybackParameterDialog"; // Minimum allowable range in ExoPlayer - private static final double MINIMUM_PLAYBACK_VALUE = 0.10f; - private static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; + private static final double MIN_PLAYBACK_VALUE = 0.10f; + private static final double MAX_PLAYBACK_VALUE = 3.00f; private static final double STEP_1_PERCENT_VALUE = 0.01f; private static final double STEP_5_PERCENT_VALUE = 0.05f; @@ -42,30 +47,42 @@ public class PlaybackParameterDialog extends DialogFragment { private static final double STEP_100_PERCENT_VALUE = 1.00f; private static final double DEFAULT_TEMPO = 1.00f; - private static final double DEFAULT_PITCH = 1.00f; + private static final double DEFAULT_PITCH_PERCENT = 1.00f; private static final double DEFAULT_STEP = STEP_25_PERCENT_VALUE; private static final boolean DEFAULT_SKIP_SILENCE = false; private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic( - MINIMUM_PLAYBACK_VALUE, - MAXIMUM_PLAYBACK_VALUE, + MIN_PLAYBACK_VALUE, + MAX_PLAYBACK_VALUE, 1.00f, 10_000); + private static final SliderStrategy SEMITONE_STRATEGY = new SliderStrategy() { + @Override + public int progressOf(final double value) { + return PlayerSemitoneHelper.percentToSemitones(value) + 12; + } + + @Override + public double valueOf(final int progress) { + return PlayerSemitoneHelper.semitonesToPercent(progress - 12); + } + }; + @Nullable private Callback callback; @State double initialTempo = DEFAULT_TEMPO; @State - double initialPitch = DEFAULT_PITCH; + double initialPitchPercent = DEFAULT_PITCH_PERCENT; @State boolean initialSkipSilence = DEFAULT_SKIP_SILENCE; @State double tempo = DEFAULT_TEMPO; @State - double pitch = DEFAULT_PITCH; + double pitchPercent = DEFAULT_PITCH_PERCENT; @State double stepSize = DEFAULT_STEP; @State @@ -83,11 +100,11 @@ public class PlaybackParameterDialog extends DialogFragment { dialog.callback = callback; dialog.initialTempo = playbackTempo; - dialog.initialPitch = playbackPitch; + dialog.initialPitchPercent = playbackPitch; dialog.initialSkipSilence = playbackSkipSilence; dialog.tempo = dialog.initialTempo; - dialog.pitch = dialog.initialPitch; + dialog.pitchPercent = dialog.initialPitchPercent; dialog.skipSilence = dialog.initialSkipSilence; return dialog; @@ -125,20 +142,19 @@ public class PlaybackParameterDialog extends DialogFragment { binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext())); initUI(); - initUIData(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) .setView(binding.getRoot()) .setCancelable(true) .setNegativeButton(R.string.cancel, (dialogInterface, i) -> { setAndUpdateTempo(initialTempo); - setAndUpdatePitch(initialPitch); + setAndUpdatePitch(initialPitchPercent); setAndUpdateSkipSilence(initialSkipSilence); updateCallback(); }) .setNeutralButton(R.string.playback_reset, (dialogInterface, i) -> { setAndUpdateTempo(DEFAULT_TEMPO); - setAndUpdatePitch(DEFAULT_PITCH); + setAndUpdatePitch(DEFAULT_PITCH_PERCENT); setAndUpdateSkipSilence(DEFAULT_SKIP_SILENCE); updateCallback(); }) @@ -153,12 +169,63 @@ public class PlaybackParameterDialog extends DialogFragment { private void initUI() { // Tempo - setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MINIMUM_PLAYBACK_VALUE); - setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAXIMUM_PLAYBACK_VALUE); + setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PLAYBACK_VALUE); + setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PLAYBACK_VALUE); - // Pitch - setText(binding.pitchMinimumText, PlayerHelper::formatPitch, MINIMUM_PLAYBACK_VALUE); - setText(binding.pitchMaximumText, PlayerHelper::formatPitch, MAXIMUM_PLAYBACK_VALUE); + binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + setAndUpdateTempo(tempo); + binding.tempoSeekbar.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener( + QUADRATIC_STRATEGY, + this::onTempoSliderUpdated)); + + registerOnStepClickListener( + binding.tempoStepDown, + () -> tempo, + -1, + this::onTempoSliderUpdated); + registerOnStepClickListener( + binding.tempoStepUp, + () -> tempo, + 1, + this::onTempoSliderUpdated); + + // Pitch - Percent + setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE); + setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE); + + binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + setAndUpdatePitch(pitchPercent); + binding.pitchPercentSeekbar.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener( + QUADRATIC_STRATEGY, + this::onPitchPercentSliderUpdated)); + + registerOnStepClickListener( + binding.pitchPercentStepDown, + () -> pitchPercent, + -1, + this::onPitchPercentSliderUpdated); + registerOnStepClickListener( + binding.pitchPercentStepUp, + () -> pitchPercent, + 1, + this::onPitchPercentSliderUpdated); + + // Pitch - Semitone + binding.pitchSemitoneSeekbar.setOnSeekBarChangeListener( + getTempoOrPitchSeekbarChangeListener( + SEMITONE_STRATEGY, + this::onPitchPercentSliderUpdated)); + + registerOnSemitoneStepClickListener( + binding.pitchSemitoneStepDown, + -1, + this::onPitchPercentSliderUpdated); + registerOnSemitoneStepClickListener( + binding.pitchSemitoneStepUp, + 1, + this::onPitchPercentSliderUpdated); // Steps setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE); @@ -166,6 +233,34 @@ public class PlaybackParameterDialog extends DialogFragment { setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE); setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); + + setAndUpdateStepSize(stepSize); + + // Bottom controls + bindCheckboxWithBoolPref( + binding.unhookCheckbox, + R.string.playback_unhook_key, + true, + isChecked -> { + if (!isChecked) { + // when unchecked, slide back to the minimum of current tempo or pitch + setSliders(Math.min(pitchPercent, tempo)); + updateCallback(); + } + }); + + setAndUpdateSkipSilence(skipSilence); + binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + skipSilence = isChecked; + updateCallback(); + }); + + bindCheckboxWithBoolPref( + binding.adjustBySemitonesCheckbox, + R.string.playback_adjust_by_semitones_key, + false, + this::showPitchSemitonesOrPercent + ); } private TextView setText( @@ -177,6 +272,31 @@ public class PlaybackParameterDialog extends DialogFragment { return textView; } + private void registerOnStepClickListener( + final TextView stepTextView, + final DoubleSupplier currentValueSupplier, + final double direction, // -1 for step down, +1 for step up + final DoubleConsumer newValueConsumer + ) { + stepTextView.setOnClickListener(view -> { + newValueConsumer.accept( + currentValueSupplier.getAsDouble() + 1 * stepSize * direction); + updateCallback(); + }); + } + + private void registerOnSemitoneStepClickListener( + final TextView stepTextView, + final int direction, // -1 for step down, +1 for step up + final DoubleConsumer newValueConsumer + ) { + stepTextView.setOnClickListener(view -> { + newValueConsumer.accept(PlayerSemitoneHelper.semitonesToPercent( + PlayerSemitoneHelper.percentToSemitones(this.pitchPercent) + direction)); + updateCallback(); + }); + } + private void setupStepTextView( final TextView textView, final double stepSizeValue @@ -185,77 +305,14 @@ public class PlaybackParameterDialog extends DialogFragment { .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); } - private void initUIData() { - // Tempo - binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); - setAndUpdateTempo(tempo); - binding.tempoSeekbar.setOnSeekBarChangeListener( - getTempoOrPitchSeekbarChangeListener(this::onTempoSliderUpdated)); - - registerOnStepClickListener( - binding.tempoStepDown, tempo, -1, this::onTempoSliderUpdated); - registerOnStepClickListener( - binding.tempoStepUp, tempo, 1, this::onTempoSliderUpdated); - - // Pitch - binding.pitchSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAXIMUM_PLAYBACK_VALUE)); - setAndUpdatePitch(pitch); - binding.pitchSeekbar.setOnSeekBarChangeListener( - getTempoOrPitchSeekbarChangeListener(this::onPitchSliderUpdated)); - - registerOnStepClickListener( - binding.pitchStepDown, pitch, -1, this::onPitchSliderUpdated); - registerOnStepClickListener( - binding.pitchStepUp, pitch, 1, this::onPitchSliderUpdated); - - // Steps - setAndUpdateStepSize(stepSize); - - // Bottom controls - // restore whether pitch and tempo are unhooked or not - binding.unhookCheckbox.setChecked(PreferenceManager - .getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.playback_unhook_key), true)); - - binding.unhookCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - // save whether pitch and tempo are unhooked or not - PreferenceManager.getDefaultSharedPreferences(requireContext()) - .edit() - .putBoolean(getString(R.string.playback_unhook_key), isChecked) - .apply(); - - if (!isChecked) { - // when unchecked, slide back to the minimum of current tempo or pitch - setSliders(Math.min(pitch, tempo)); - } - }); - - setAndUpdateSkipSilence(skipSilence); - binding.skipSilenceCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - skipSilence = isChecked; - updateCallback(); - }); - } - - private void registerOnStepClickListener( - final TextView stepTextView, - final double currentValue, - final double direction, // -1 for step down, +1 for step up - final DoubleConsumer newValueConsumer - ) { - stepTextView.setOnClickListener(view -> - newValueConsumer.accept(currentValue * direction) - ); - } - private void setAndUpdateStepSize(final double newStepSize) { this.stepSize = newStepSize; binding.tempoStepUp.setText(getStepUpPercentString(newStepSize)); binding.tempoStepDown.setText(getStepDownPercentString(newStepSize)); - binding.pitchStepUp.setText(getStepUpPercentString(newStepSize)); - binding.pitchStepDown.setText(getStepDownPercentString(newStepSize)); + binding.pitchPercentStepUp.setText(getStepUpPercentString(newStepSize)); + binding.pitchPercentStepDown.setText(getStepDownPercentString(newStepSize)); } private void setAndUpdateSkipSilence(final boolean newSkipSilence) { @@ -263,19 +320,72 @@ public class PlaybackParameterDialog extends DialogFragment { binding.skipSilenceCheckbox.setChecked(newSkipSilence); } + private void bindCheckboxWithBoolPref( + @NonNull final CheckBox checkBox, + @StringRes final int resId, + final boolean defaultValue, + @Nullable final Consumer onInitialValueOrValueChange + ) { + final boolean prefValue = PreferenceManager + .getDefaultSharedPreferences(requireContext()) + .getBoolean(getString(resId), defaultValue); + + checkBox.setChecked(prefValue); + + if (onInitialValueOrValueChange != null) { + onInitialValueOrValueChange.accept(prefValue); + } + + checkBox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + // save whether pitch and tempo are unhooked or not + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .edit() + .putBoolean(getString(resId), isChecked) + .apply(); + + if (onInitialValueOrValueChange != null) { + onInitialValueOrValueChange.accept(isChecked); + } + }); + } + + private void showPitchSemitonesOrPercent(final boolean semitones) { + binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE); + binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE); + + if (semitones) { + // Recalculate pitch percent when changing to semitone + // (as it could be an invalid semitone value) + final double newPitchPercent = calcValidPitch(pitchPercent); + + // If the values differ set the new pitch + if (this.pitchPercent != newPitchPercent) { + if (DEBUG) { + Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: " + + "currentPitchPercent = " + pitchPercent + ", " + + "newPitchPercent = " + newPitchPercent + ); + } + this.onPitchPercentSliderUpdated(newPitchPercent); + updateCallback(); + } + } + } + /*////////////////////////////////////////////////////////////////////////// // Sliders //////////////////////////////////////////////////////////////////////////*/ private SeekBar.OnSeekBarChangeListener getTempoOrPitchSeekbarChangeListener( + final SliderStrategy sliderStrategy, final DoubleConsumer newValueConsumer ) { return new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) { - if (fromUser) { // this change is first in chain - newValueConsumer.accept(QUADRATIC_STRATEGY.valueOf(progress)); + if (fromUser) { // ensure that the user triggered the change + newValueConsumer.accept(sliderStrategy.valueOf(progress)); updateCallback(); } } @@ -300,7 +410,7 @@ public class PlaybackParameterDialog extends DialogFragment { } } - private void onPitchSliderUpdated(final double newPitch) { + private void onPitchPercentSliderUpdated(final double newPitch) { if (!binding.unhookCheckbox.isChecked()) { setSliders(newPitch); } else { @@ -314,15 +424,39 @@ public class PlaybackParameterDialog extends DialogFragment { } private void setAndUpdateTempo(final double newTempo) { - this.tempo = newTempo; + this.tempo = calcValidTempo(newTempo); + binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo); } private void setAndUpdatePitch(final double newPitch) { - this.pitch = newPitch; - binding.pitchSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitch)); - setText(binding.pitchCurrentText, PlayerHelper::formatPitch, pitch); + this.pitchPercent = calcValidPitch(newPitch); + + binding.pitchPercentSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(pitchPercent)); + binding.pitchSemitoneSeekbar.setProgress(SEMITONE_STRATEGY.progressOf(pitchPercent)); + setText(binding.pitchPercentCurrentText, + PlayerHelper::formatPitch, + pitchPercent); + setText(binding.pitchSemitoneCurrentText, + PlayerSemitoneHelper::formatPitchSemitones, + pitchPercent); + } + + private double calcValidTempo(final double newTempo) { + return Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newTempo)); + } + + private double calcValidPitch(final double newPitch) { + final double calcPitch = + Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch)); + + if (!binding.adjustBySemitonesCheckbox.isChecked()) { + return calcPitch; + } + + return PlayerSemitoneHelper.semitonesToPercent( + PlayerSemitoneHelper.percentToSemitones(calcPitch)); } /*////////////////////////////////////////////////////////////////////////// @@ -335,12 +469,12 @@ public class PlaybackParameterDialog extends DialogFragment { } if (DEBUG) { Log.d(TAG, "Updating callback: " - + "tempo = [" + tempo + "], " - + "pitch = [" + pitch + "], " - + "skipSilence = [" + skipSilence + "]" + + "tempo = " + tempo + ", " + + "pitchPercent = " + pitchPercent + ", " + + "skipSilence = " + skipSilence ); } - callback.onPlaybackParameterChanged((float) tempo, (float) pitch, skipSilence); + callback.onPlaybackParameterChanged((float) tempo, (float) pitchPercent, skipSilence); } @NonNull diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java new file mode 100644 index 000000000..abbcc2c82 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java @@ -0,0 +1,37 @@ +package org.schabi.newpipe.player.helper; + +/** + * Converts between percent and 12-tone equal temperament semitones. + *
+ * @see + * + * Wikipedia: Equal temperament#Twelve-tone equal temperament + * + */ +public final class PlayerSemitoneHelper { + public static final int TONES = 12; + + private PlayerSemitoneHelper() { + // No impl + } + + public static String formatPitchSemitones(final double percent) { + return formatPitchSemitones(percentToSemitones(percent)); + } + + public static String formatPitchSemitones(final int semitones) { + return semitones > 0 ? "+" + semitones : "" + semitones; + } + + public static double semitonesToPercent(final int semitones) { + return Math.pow(2, ensureSemitonesInRange(semitones) / (double) TONES); + } + + public static int percentToSemitones(final double percent) { + return ensureSemitonesInRange((int) Math.round(TONES * Math.log(percent) / Math.log(2))); + } + + private static int ensureSemitonesInRange(final int semitones) { + return Math.max(-TONES, Math.min(TONES, semitones)); + } +} From 321cf8bf7d7a419913f58697cb4cb7e5630212d7 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 21:33:21 +0100 Subject: [PATCH 063/992] Fine tuned dialog --- .../res/layout/dialog_playback_parameter.xml | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 75269ffd3..640475f39 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -1,5 +1,6 @@ + android:orientation="horizontal" + tools:visibility="gone"> + tools:text="0" + tools:ignore="RelativeOverlap" /> + android:textColor="?attr/colorAccent" + tools:text="1%" /> + android:textColor="?attr/colorAccent" + tools:text="5%" /> + android:textColor="?attr/colorAccent" + tools:text="10%" /> + android:textColor="?attr/colorAccent" + tools:text="25%" /> - + android:textColor="?attr/colorAccent" + tools:text="100%" /> Date: Fri, 4 Mar 2022 21:34:45 +0100 Subject: [PATCH 064/992] Code improvements regarding stepSize --- .../helper/PlaybackParameterDialog.java | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 709216ece..eab64e483 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -84,8 +84,6 @@ public class PlaybackParameterDialog extends DialogFragment { @State double pitchPercent = DEFAULT_PITCH_PERCENT; @State - double stepSize = DEFAULT_STEP; - @State boolean skipSilence = DEFAULT_SKIP_SILENCE; private DialogPlaybackParameterBinding binding; @@ -228,13 +226,10 @@ public class PlaybackParameterDialog extends DialogFragment { this::onPitchPercentSliderUpdated); // Steps - setupStepTextView(binding.stepSizeOnePercent, STEP_1_PERCENT_VALUE); - setupStepTextView(binding.stepSizeFivePercent, STEP_5_PERCENT_VALUE); - setupStepTextView(binding.stepSizeTenPercent, STEP_10_PERCENT_VALUE); - setupStepTextView(binding.stepSizeTwentyFivePercent, STEP_25_PERCENT_VALUE); - setupStepTextView(binding.stepSizeOneHundredPercent, STEP_100_PERCENT_VALUE); - - setAndUpdateStepSize(stepSize); + getStepSizeComponentMappings() + .forEach(this::setupStepTextView); + // Initialize UI + setStepSizeToUI(getCurrentStepSize()); // Bottom controls bindCheckboxWithBoolPref( @@ -263,13 +258,12 @@ public class PlaybackParameterDialog extends DialogFragment { ); } - private TextView setText( + private void setText( final TextView textView, final DoubleFunction formatter, final double value ) { Objects.requireNonNull(textView).setText(formatter.apply(value)); - return textView; } private void registerOnStepClickListener( @@ -280,7 +274,7 @@ public class PlaybackParameterDialog extends DialogFragment { ) { stepTextView.setOnClickListener(view -> { newValueConsumer.accept( - currentValueSupplier.getAsDouble() + 1 * stepSize * direction); + currentValueSupplier.getAsDouble() + 1 * getCurrentStepSize() * direction); updateCallback(); }); } @@ -315,16 +309,22 @@ public class PlaybackParameterDialog extends DialogFragment { binding.pitchPercentStepDown.setText(getStepDownPercentString(newStepSize)); } + private double getCurrentStepSize() { + return PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP); + } + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { this.skipSilence = newSkipSilence; binding.skipSilenceCheckbox.setChecked(newSkipSilence); } + @SuppressWarnings("SameParameterValue") // this method was written to be reusable private void bindCheckboxWithBoolPref( @NonNull final CheckBox checkBox, @StringRes final int resId, final boolean defaultValue, - @Nullable final Consumer onInitialValueOrValueChange + @NonNull final Consumer onInitialValueOrValueChange ) { final boolean prefValue = PreferenceManager .getDefaultSharedPreferences(requireContext()) @@ -332,9 +332,7 @@ public class PlaybackParameterDialog extends DialogFragment { checkBox.setChecked(prefValue); - if (onInitialValueOrValueChange != null) { - onInitialValueOrValueChange.accept(prefValue); - } + onInitialValueOrValueChange.accept(prefValue); checkBox.setOnCheckedChangeListener((compoundButton, isChecked) -> { // save whether pitch and tempo are unhooked or not @@ -343,9 +341,7 @@ public class PlaybackParameterDialog extends DialogFragment { .putBoolean(getString(resId), isChecked) .apply(); - if (onInitialValueOrValueChange != null) { - onInitialValueOrValueChange.accept(isChecked); - } + onInitialValueOrValueChange.accept(isChecked); }); } From 4b0653658273f456ddbc56a86b1695073d982895 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 21:37:11 +0100 Subject: [PATCH 065/992] Reworked switching to semitones Using an expandable Tab-like component instead of a combobox --- .../schabi/newpipe/local/feed/FeedFragment.kt | 16 +- .../helper/PlaybackParameterDialog.java | 169 ++++++++++++++---- .../schabi/newpipe/util/DrawableResolver.kt | 26 +++ .../res/layout/dialog_playback_parameter.xml | 59 ++++-- app/src/main/res/values/strings.xml | 2 + 5 files changed, 209 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index e97629f31..e8e78feda 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -25,7 +25,6 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.graphics.Typeface -import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.os.Bundle import android.os.Parcelable @@ -37,7 +36,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.Button -import androidx.annotation.AttrRes import androidx.annotation.Nullable import androidx.appcompat.app.AlertDialog import androidx.appcompat.content.res.AppCompatResources @@ -77,6 +75,7 @@ import org.schabi.newpipe.local.feed.item.StreamItem import org.schabi.newpipe.local.feed.service.FeedLoadService import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.util.DeviceUtils +import org.schabi.newpipe.util.DrawableResolver.Companion.resolveDrawable import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams @@ -579,19 +578,6 @@ class FeedFragment : BaseStateFragment() { lastNewItemsCount = highlightCount } - private fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { - return androidx.core.content.ContextCompat.getDrawable( - context, - android.util.TypedValue().apply { - context.theme.resolveAttribute( - attrResId, - this, - true - ) - }.resourceId - ) - } - private fun showNewItemsLoaded() { tryGetNewItemsLoadedButton()?.clearAnimation() tryGetNewItemsLoadedButton() diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index eab64e483..902222cc5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -1,10 +1,14 @@ package org.schabi.newpipe.player.helper; +import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; import static org.schabi.newpipe.player.Player.DEBUG; +import static org.schabi.newpipe.util.DrawableResolver.resolveDrawable; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import android.app.Dialog; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -22,8 +26,11 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.SliderStrategy; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.function.Consumer; import java.util.function.DoubleConsumer; @@ -40,6 +47,9 @@ public class PlaybackParameterDialog extends DialogFragment { private static final double MIN_PLAYBACK_VALUE = 0.10f; private static final double MAX_PLAYBACK_VALUE = 3.00f; + private static final boolean PITCH_CTRL_MODE_PERCENT = false; + private static final boolean PITCH_CTRL_MODE_SEMITONE = true; + private static final double STEP_1_PERCENT_VALUE = 0.01f; private static final double STEP_5_PERCENT_VALUE = 0.05f; private static final double STEP_10_PERCENT_VALUE = 0.10f; @@ -188,6 +198,22 @@ public class PlaybackParameterDialog extends DialogFragment { 1, this::onTempoSliderUpdated); + // Pitch + binding.pitchToogleControlModes.setOnClickListener(v -> { + final boolean isCurrentlyVisible = + binding.pitchControlModeTabs.getVisibility() == View.GONE; + binding.pitchControlModeTabs.setVisibility(isCurrentlyVisible + ? View.VISIBLE + : View.GONE); + animateRotation(binding.pitchToogleControlModes, + Player.DEFAULT_CONTROLS_DURATION, + isCurrentlyVisible ? 180 : 0); + }); + + getPitchControlModeComponentMappings() + .forEach(this::setupPitchControlModeTextView); + changePitchControlMode(isCurrentPitchControlModeSemitone()); + // Pitch - Percent setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE); setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE); @@ -249,13 +275,6 @@ public class PlaybackParameterDialog extends DialogFragment { skipSilence = isChecked; updateCallback(); }); - - bindCheckboxWithBoolPref( - binding.adjustBySemitonesCheckbox, - R.string.playback_adjust_by_semitones_key, - false, - this::showPitchSemitonesOrPercent - ); } private void setText( @@ -291,17 +310,114 @@ public class PlaybackParameterDialog extends DialogFragment { }); } - private void setupStepTextView( - final TextView textView, - final double stepSizeValue + private void setupPitchControlModeTextView( + final boolean semitones, + final TextView textView ) { - setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue) - .setOnClickListener(view -> setAndUpdateStepSize(stepSizeValue)); + textView.setOnClickListener(view -> { + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .edit() + .putBoolean(getString(R.string.playback_adjust_by_semitones_key), semitones) + .apply(); + + changePitchControlMode(semitones); + }); } - private void setAndUpdateStepSize(final double newStepSize) { - this.stepSize = newStepSize; + private Map getPitchControlModeComponentMappings() { + final Map mappings = new HashMap<>(); + mappings.put(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent); + mappings.put(PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone); + return mappings; + } + private void changePitchControlMode(final boolean semitones) { + // Bring all textviews into a normal state + final Map pitchCtrlModeComponentMapping = + getPitchControlModeComponentMappings(); + pitchCtrlModeComponentMapping.forEach((v, textView) -> textView.setBackground( + resolveDrawable(requireContext(), R.attr.selectableItemBackground))); + + // Mark the selected textview + final TextView textView = pitchCtrlModeComponentMapping.get(semitones); + if (textView != null) { + textView.setBackground(new LayerDrawable(new Drawable[]{ + resolveDrawable(requireContext(), R.attr.dashed_border), + resolveDrawable(requireContext(), R.attr.selectableItemBackground) + })); + } + + // Show or hide component + binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE); + binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE); + + if (semitones) { + // Recalculate pitch percent when changing to semitone + // (as it could be an invalid semitone value) + final double newPitchPercent = calcValidPitch(pitchPercent); + + // If the values differ set the new pitch + if (this.pitchPercent != newPitchPercent) { + if (DEBUG) { + Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: " + + "currentPitchPercent = " + pitchPercent + ", " + + "newPitchPercent = " + newPitchPercent + ); + } + this.onPitchPercentSliderUpdated(newPitchPercent); + updateCallback(); + } + } + } + + private boolean isCurrentPitchControlModeSemitone() { + return PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getBoolean( + getString(R.string.playback_adjust_by_semitones_key), + PITCH_CTRL_MODE_PERCENT); + } + + private void setupStepTextView( + final double stepSizeValue, + final TextView textView + ) { + setText(textView, PlaybackParameterDialog::getPercentString, stepSizeValue); + textView.setOnClickListener(view -> { + PreferenceManager.getDefaultSharedPreferences(requireContext()) + .edit() + .putFloat(getString(R.string.adjustment_step_key), (float) stepSizeValue) + .apply(); + + setStepSizeToUI(stepSizeValue); + }); + } + + private Map getStepSizeComponentMappings() { + final Map mappings = new HashMap<>(); + mappings.put(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent); + mappings.put(STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent); + mappings.put(STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent); + mappings.put(STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent); + mappings.put(STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent); + return mappings; + } + + private void setStepSizeToUI(final double newStepSize) { + // Bring all textviews into a normal state + final Map stepSiteComponentMapping = getStepSizeComponentMappings(); + stepSiteComponentMapping.forEach((v, textView) -> textView.setBackground( + resolveDrawable(requireContext(), R.attr.selectableItemBackground))); + + // Mark the selected textview + final TextView textView = stepSiteComponentMapping.get(newStepSize); + if (textView != null) { + textView.setBackground(new LayerDrawable(new Drawable[]{ + resolveDrawable(requireContext(), R.attr.dashed_border), + resolveDrawable(requireContext(), R.attr.selectableItemBackground) + })); + } + + // Bind to the corresponding control components binding.tempoStepUp.setText(getStepUpPercentString(newStepSize)); binding.tempoStepDown.setText(getStepDownPercentString(newStepSize)); @@ -345,29 +461,6 @@ public class PlaybackParameterDialog extends DialogFragment { }); } - private void showPitchSemitonesOrPercent(final boolean semitones) { - binding.pitchPercentControl.setVisibility(semitones ? View.GONE : View.VISIBLE); - binding.pitchSemitoneControl.setVisibility(semitones ? View.VISIBLE : View.GONE); - - if (semitones) { - // Recalculate pitch percent when changing to semitone - // (as it could be an invalid semitone value) - final double newPitchPercent = calcValidPitch(pitchPercent); - - // If the values differ set the new pitch - if (this.pitchPercent != newPitchPercent) { - if (DEBUG) { - Log.d(TAG, "Bringing pitchPercent to correct corresponding semitone: " - + "currentPitchPercent = " + pitchPercent + ", " - + "newPitchPercent = " + newPitchPercent - ); - } - this.onPitchPercentSliderUpdated(newPitchPercent); - updateCallback(); - } - } - } - /*////////////////////////////////////////////////////////////////////////// // Sliders //////////////////////////////////////////////////////////////////////////*/ @@ -447,7 +540,7 @@ public class PlaybackParameterDialog extends DialogFragment { final double calcPitch = Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch)); - if (!binding.adjustBySemitonesCheckbox.isChecked()) { + if (!isCurrentPitchControlModeSemitone()) { return calcPitch; } diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt new file mode 100644 index 000000000..50f875257 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt @@ -0,0 +1,26 @@ +package org.schabi.newpipe.util + +import android.content.Context +import android.graphics.drawable.Drawable +import androidx.annotation.AttrRes + +/** + * Utility class for resolving [Drawables](Drawable) + */ +class DrawableResolver { + companion object { + @JvmStatic + fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { + return androidx.core.content.ContextCompat.getDrawable( + context, + android.util.TypedValue().apply { + context.theme.resolveAttribute( + attrResId, + this, + true + ) + }.resourceId + ) + } + } +} diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml index 640475f39..e402f4fb1 100644 --- a/app/src/main/res/layout/dialog_playback_parameter.xml +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -146,11 +146,59 @@ android:textColor="?attr/colorAccent" android:textStyle="bold" /> + + + + + + + + + + - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 792e6414b..af2921cca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -501,6 +501,8 @@ Step Tempo step Reset + Percent + Semitone In order to comply with the European General Data Protection Regulation (GDPR), we hereby draw your attention to NewPipe\'s privacy policy. Please read it carefully. \nYou must accept it to send us the bug report. From 20602889be38e38c25e6022f56be83860ef34313 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 22:02:39 +0100 Subject: [PATCH 066/992] Added some doc and abstracted more methods --- .../helper/PlaybackParameterDialog.java | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 902222cc5..4ab8f9248 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -172,7 +172,7 @@ public class PlaybackParameterDialog extends DialogFragment { } /*////////////////////////////////////////////////////////////////////////// - // Control Views + // UI Initialization and Control //////////////////////////////////////////////////////////////////////////*/ private void initUI() { @@ -265,8 +265,7 @@ public class PlaybackParameterDialog extends DialogFragment { isChecked -> { if (!isChecked) { // when unchecked, slide back to the minimum of current tempo or pitch - setSliders(Math.min(pitchPercent, tempo)); - updateCallback(); + ensureHookIsValidAndUpdateCallBack(); } }); @@ -277,6 +276,8 @@ public class PlaybackParameterDialog extends DialogFragment { }); } + // -- General formatting -- + private void setText( final TextView textView, final DoubleFunction formatter, @@ -285,6 +286,8 @@ public class PlaybackParameterDialog extends DialogFragment { Objects.requireNonNull(textView).setText(formatter.apply(value)); } + // -- Steps -- + private void registerOnStepClickListener( final TextView stepTextView, final DoubleSupplier currentValueSupplier, @@ -310,6 +313,8 @@ public class PlaybackParameterDialog extends DialogFragment { }); } + // -- Pitch -- + private void setupPitchControlModeTextView( final boolean semitones, final TextView textView @@ -367,6 +372,9 @@ public class PlaybackParameterDialog extends DialogFragment { this.onPitchPercentSliderUpdated(newPitchPercent); updateCallback(); } + } else if (!binding.unhookCheckbox.isChecked()) { + // When changing to percent it's possible that tempo is != pitch + ensureHookIsValidAndUpdateCallBack(); } } @@ -377,6 +385,8 @@ public class PlaybackParameterDialog extends DialogFragment { PITCH_CTRL_MODE_PERCENT); } + // -- Steps (Set) -- + private void setupStepTextView( final double stepSizeValue, final TextView textView @@ -430,6 +440,8 @@ public class PlaybackParameterDialog extends DialogFragment { .getFloat(getString(R.string.adjustment_step_key), (float) DEFAULT_STEP); } + // -- Additional options -- + private void setAndUpdateSkipSilence(final boolean newSkipSilence) { this.skipSilence = newSkipSilence; binding.skipSilenceCheckbox.setChecked(newSkipSilence); @@ -461,6 +473,18 @@ public class PlaybackParameterDialog extends DialogFragment { }); } + /** + * Ensures that the slider hook is valid and if not sets and updates the sliders accordingly. + *
+ * You have to ensure by yourself that the hooking is active. + */ + private void ensureHookIsValidAndUpdateCallBack() { + if (tempo != pitchPercent) { + setSliders(Math.min(tempo, pitchPercent)); + updateCallback(); + } + } + /*////////////////////////////////////////////////////////////////////////// // Sliders //////////////////////////////////////////////////////////////////////////*/ From 1b8c517e3ea1924787eeff075a575414a5f3b544 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 4 Mar 2022 22:21:17 +0100 Subject: [PATCH 067/992] Removed unused strings --- app/src/main/res/values/strings.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index af2921cca..1f87ae9fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -497,9 +497,7 @@ Pitch Unhook (may cause distortion) Fast-forward during silence - Adjust pitch by musical semitones Step - Tempo step Reset Percent Semitone From 44dada9e60b23f500c3904af8578a874e78c0c5a Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 6 Mar 2022 16:10:42 +0100 Subject: [PATCH 068/992] Use better Kotlin syntax From the PR review --- .../schabi/newpipe/local/feed/FeedFragment.kt | 2 +- .../schabi/newpipe/util/DrawableResolver.kt | 29 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index e8e78feda..55810284f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -75,7 +75,7 @@ import org.schabi.newpipe.local.feed.item.StreamItem import org.schabi.newpipe.local.feed.service.FeedLoadService import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.util.DeviceUtils -import org.schabi.newpipe.util.DrawableResolver.Companion.resolveDrawable +import org.schabi.newpipe.util.DrawableResolver.resolveDrawable import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt index 50f875257..ccc9e7dd4 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt +++ b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt @@ -2,25 +2,24 @@ package org.schabi.newpipe.util import android.content.Context import android.graphics.drawable.Drawable +import android.util.TypedValue import androidx.annotation.AttrRes /** * Utility class for resolving [Drawables](Drawable) */ -class DrawableResolver { - companion object { - @JvmStatic - fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { - return androidx.core.content.ContextCompat.getDrawable( - context, - android.util.TypedValue().apply { - context.theme.resolveAttribute( - attrResId, - this, - true - ) - }.resourceId - ) - } +object DrawableResolver { + @JvmStatic + fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { + return androidx.core.content.ContextCompat.getDrawable( + context, + TypedValue().apply { + context.theme.resolveAttribute( + attrResId, + this, + true + ) + }.resourceId + ) } } From b9190eddfe1563cc5269caf2ca90a997db85a920 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Mon, 7 Mar 2022 20:30:25 +0100 Subject: [PATCH 069/992] Update DrawableResolver.kt Nicer import :wink: --- app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt index ccc9e7dd4..8a728bfbf 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt +++ b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.drawable.Drawable import android.util.TypedValue import androidx.annotation.AttrRes +import androidx.core.content.ContextCompat /** * Utility class for resolving [Drawables](Drawable) @@ -11,7 +12,7 @@ import androidx.annotation.AttrRes object DrawableResolver { @JvmStatic fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { - return androidx.core.content.ContextCompat.getDrawable( + return ContextCompat.getDrawable( context, TypedValue().apply { context.theme.resolveAttribute( From 0f551baf3729121dadc0597d615aab58ea13e941 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Wed, 16 Mar 2022 14:53:33 +0100 Subject: [PATCH 070/992] Refactored code --- .../helper/PlaybackParameterDialog.java | 24 +++++++++---------- .../player/helper/PlayerSemitoneHelper.java | 9 +++---- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 4ab8f9248..26caa1b20 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -44,8 +44,8 @@ public class PlaybackParameterDialog extends DialogFragment { private static final String TAG = "PlaybackParameterDialog"; // Minimum allowable range in ExoPlayer - private static final double MIN_PLAYBACK_VALUE = 0.10f; - private static final double MAX_PLAYBACK_VALUE = 3.00f; + private static final double MIN_PITCH_OR_SPEED = 0.10f; + private static final double MAX_PITCH_OR_SPEED = 3.00f; private static final boolean PITCH_CTRL_MODE_PERCENT = false; private static final boolean PITCH_CTRL_MODE_SEMITONE = true; @@ -62,8 +62,8 @@ public class PlaybackParameterDialog extends DialogFragment { private static final boolean DEFAULT_SKIP_SILENCE = false; private static final SliderStrategy QUADRATIC_STRATEGY = new SliderStrategy.Quadratic( - MIN_PLAYBACK_VALUE, - MAX_PLAYBACK_VALUE, + MIN_PITCH_OR_SPEED, + MAX_PITCH_OR_SPEED, 1.00f, 10_000); @@ -177,10 +177,10 @@ public class PlaybackParameterDialog extends DialogFragment { private void initUI() { // Tempo - setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PLAYBACK_VALUE); - setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PLAYBACK_VALUE); + setText(binding.tempoMinimumText, PlayerHelper::formatSpeed, MIN_PITCH_OR_SPEED); + setText(binding.tempoMaximumText, PlayerHelper::formatSpeed, MAX_PITCH_OR_SPEED); - binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + binding.tempoSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED)); setAndUpdateTempo(tempo); binding.tempoSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener( @@ -215,10 +215,10 @@ public class PlaybackParameterDialog extends DialogFragment { changePitchControlMode(isCurrentPitchControlModeSemitone()); // Pitch - Percent - setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PLAYBACK_VALUE); - setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PLAYBACK_VALUE); + setText(binding.pitchPercentMinimumText, PlayerHelper::formatPitch, MIN_PITCH_OR_SPEED); + setText(binding.pitchPercentMaximumText, PlayerHelper::formatPitch, MAX_PITCH_OR_SPEED); - binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PLAYBACK_VALUE)); + binding.pitchPercentSeekbar.setMax(QUADRATIC_STRATEGY.progressOf(MAX_PITCH_OR_SPEED)); setAndUpdatePitch(pitchPercent); binding.pitchPercentSeekbar.setOnSeekBarChangeListener( getTempoOrPitchSeekbarChangeListener( @@ -557,12 +557,12 @@ public class PlaybackParameterDialog extends DialogFragment { } private double calcValidTempo(final double newTempo) { - return Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newTempo)); + return Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newTempo)); } private double calcValidPitch(final double newPitch) { final double calcPitch = - Math.max(MIN_PLAYBACK_VALUE, Math.min(MAX_PLAYBACK_VALUE, newPitch)); + Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newPitch)); if (!isCurrentPitchControlModeSemitone()) { return calcPitch; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java index abbcc2c82..f3a71d7cd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java @@ -9,7 +9,7 @@ package org.schabi.newpipe.player.helper; * */ public final class PlayerSemitoneHelper { - public static final int TONES = 12; + public static final int SEMITONE_COUNT = 12; private PlayerSemitoneHelper() { // No impl @@ -24,14 +24,15 @@ public final class PlayerSemitoneHelper { } public static double semitonesToPercent(final int semitones) { - return Math.pow(2, ensureSemitonesInRange(semitones) / (double) TONES); + return Math.pow(2, ensureSemitonesInRange(semitones) / (double) SEMITONE_COUNT); } public static int percentToSemitones(final double percent) { - return ensureSemitonesInRange((int) Math.round(TONES * Math.log(percent) / Math.log(2))); + return ensureSemitonesInRange( + (int) Math.round(SEMITONE_COUNT * Math.log(percent) / Math.log(2))); } private static int ensureSemitonesInRange(final int semitones) { - return Math.max(-TONES, Math.min(TONES, semitones)); + return Math.max(-SEMITONE_COUNT, Math.min(SEMITONE_COUNT, semitones)); } } From 1dc146322c5666f2a4af9f9d14b5f2aa56626b32 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Thu, 17 Mar 2022 18:34:44 +0100 Subject: [PATCH 071/992] Merged ``DrawableResolver`` into ``ThemeHelper`` --- .../schabi/newpipe/local/feed/FeedFragment.kt | 2 +- .../helper/PlaybackParameterDialog.java | 2 +- .../schabi/newpipe/util/DrawableResolver.kt | 26 ------------------- .../org/schabi/newpipe/util/ThemeHelper.java | 18 +++++++++++++ 4 files changed, 20 insertions(+), 28 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 55810284f..b291aa035 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -75,10 +75,10 @@ import org.schabi.newpipe.local.feed.item.StreamItem import org.schabi.newpipe.local.feed.service.FeedLoadService import org.schabi.newpipe.local.subscription.SubscriptionManager import org.schabi.newpipe.util.DeviceUtils -import org.schabi.newpipe.util.DrawableResolver.resolveDrawable import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.ThemeHelper.getGridSpanCountStreams +import org.schabi.newpipe.util.ThemeHelper.resolveDrawable import org.schabi.newpipe.util.ThemeHelper.shouldUseGridLayout import java.time.OffsetDateTime import java.util.function.Consumer diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 26caa1b20..62446b50e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -2,8 +2,8 @@ package org.schabi.newpipe.player.helper; import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; import static org.schabi.newpipe.player.Player.DEBUG; -import static org.schabi.newpipe.util.DrawableResolver.resolveDrawable; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; +import static org.schabi.newpipe.util.ThemeHelper.resolveDrawable; import android.app.Dialog; import android.content.Context; diff --git a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt b/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt deleted file mode 100644 index 8a728bfbf..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/DrawableResolver.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.schabi.newpipe.util - -import android.content.Context -import android.graphics.drawable.Drawable -import android.util.TypedValue -import androidx.annotation.AttrRes -import androidx.core.content.ContextCompat - -/** - * Utility class for resolving [Drawables](Drawable) - */ -object DrawableResolver { - @JvmStatic - fun resolveDrawable(context: Context, @AttrRes attrResId: Int): Drawable? { - return ContextCompat.getDrawable( - context, - TypedValue().apply { - context.theme.resolveAttribute( - attrResId, - this, - true - ) - }.resourceId - ) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index 7c47d387f..7d06e57b6 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -23,9 +23,11 @@ import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.drawable.Drawable; import android.util.TypedValue; import androidx.annotation.AttrRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.appcompat.app.ActionBar; @@ -227,6 +229,22 @@ public final class ThemeHelper { return value.data; } + /** + * Resolves a {@link Drawable} by it's id. + * + * @param context Context + * @param attrResId Resource id + * @return the {@link Drawable} + */ + public static Drawable resolveDrawable( + @NonNull final Context context, + @AttrRes final int attrResId + ) { + final TypedValue typedValue = new TypedValue(); + context.getTheme().resolveAttribute(attrResId, typedValue, true); + return ContextCompat.getDrawable(context, typedValue.resourceId); + } + private static String getSelectedThemeKey(final Context context) { final String themeKey = context.getString(R.string.theme_key); final String defaultTheme = context.getResources().getString(R.string.default_theme_value); From a311519314085d5e79e1a55afc7c16dc744cb57b Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sat, 16 Apr 2022 21:24:01 +0200 Subject: [PATCH 072/992] Fix merge conflicts --- .../player/helper/PlaybackParameterDialog.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 62446b50e..2d1461aaf 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -27,6 +27,7 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; import java.util.HashMap; @@ -37,6 +38,8 @@ import java.util.function.DoubleConsumer; import java.util.function.DoubleFunction; import java.util.function.DoubleSupplier; +import javax.annotation.Nonnull; + import icepick.Icepick; import icepick.State; @@ -493,25 +496,16 @@ public class PlaybackParameterDialog extends DialogFragment { final SliderStrategy sliderStrategy, final DoubleConsumer newValueConsumer ) { - return new SeekBar.OnSeekBarChangeListener() { + return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(final SeekBar seekBar, final int progress, + public void onProgressChanged(@Nonnull final SeekBar seekBar, + final int progress, final boolean fromUser) { if (fromUser) { // ensure that the user triggered the change newValueConsumer.accept(sliderStrategy.valueOf(progress)); updateCallback(); } } - - @Override - public void onStartTrackingTouch(final SeekBar seekBar) { - // Do nothing - } - - @Override - public void onStopTrackingTouch(final SeekBar seekBar) { - // Do nothing - } }; } From 05d5ef602c127bce3c125dd8a63a207ba32d29b3 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 21:19:10 +0200 Subject: [PATCH 073/992] Fix proguard rules to keep Notifications settings fragment --- app/proguard-rules.pro | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 53a9ecd5a..4a54d8992 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -51,3 +51,6 @@ private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); } + +# for some reason NotificationModeConfigFragment wasn't kept (only referenced in a preference xml) +-keep class org.schabi.newpipe.settings.notifications.** { *; } From 4904b48f5c26358f74917d0c6a7b0541d91cd9d3 Mon Sep 17 00:00:00 2001 From: ZiyanZHANG <2227437462@qq.com> Date: Sun, 17 Apr 2022 18:15:13 +0800 Subject: [PATCH 074/992] Update PlayQueueActivity.java --- .../java/org/schabi/newpipe/player/PlayQueueActivity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 53e6ce591..676d63458 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -97,7 +97,10 @@ public final class PlayQueueActivity extends AppCompatActivity getMenuInflater().inflate(R.menu.menu_play_queue, m); getMenuInflater().inflate(R.menu.menu_play_queue_bg, m); onMaybeMuteChanged(); - onPlaybackParameterChanged(player.getPlaybackParameters()); + // to avoid null reference + if (player != null) { + onPlaybackParameterChanged(player.getPlaybackParameters()); + } return true; } From 8ea98b64aa81ac15f3d01e2764be38b50220644e Mon Sep 17 00:00:00 2001 From: LingYinTianMeng <2632252014@qq.com> Date: Sun, 17 Apr 2022 22:23:03 +0800 Subject: [PATCH 075/992] fix issue #7563 --- .../local/playlist/LocalPlaylistFragment.java | 15 +++++++++++---- 1 file changed, 11 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 0eb56d716..7cd2a3ec1 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 @@ -414,14 +414,21 @@ public class LocalPlaylistFragment extends BaseLocalListFragment streamStatesIter = recordManager .loadLocalStreamStateBatch(playlist).blockingGet().iterator(); - while (playlistIter.hasNext()) { final PlaylistStreamEntry playlistItem = playlistIter.next(); final int indexInHistory = Collections.binarySearch(historyStreamIds, playlistItem.getStreamId()); - - final boolean hasState = streamStatesIter.next() != null; - if (indexInHistory < 0 || hasState) { + final StreamStateEntity streamStateEntity = streamStatesIter.next(); + final long duration = playlistItem.toStreamInfoItem().getDuration(); + boolean isFinished = false; + if (streamStateEntity != null) { + isFinished = streamStateEntity.isFinished(duration); + } + final boolean isNotWatchedItem = (streamStateEntity != null + && !isFinished); + if (indexInHistory < 0) { + notWatchedItems.add(playlistItem); + } else if (isNotWatchedItem) { notWatchedItems.add(playlistItem); } else if (!thumbnailVideoRemoved && playlistManager.getPlaylistThumbnail(playlistId) From 4917da2d2e533f08c0050ba45151fb714a7adf7e Mon Sep 17 00:00:00 2001 From: karyogamy Date: Sun, 17 Apr 2022 13:26:39 -0400 Subject: [PATCH 076/992] fixed: disabled caption to no longer automatically re-enable on new player instance. --- .../main/java/org/schabi/newpipe/player/Player.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 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 82ae0df27..9f95aa519 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -3557,14 +3557,19 @@ public final class Player implements } // apply caption language from previous user preference - final List selectedPreferredLanguages = - trackSelector.getParameters().preferredTextLanguages; final String userPreferredLanguage = prefs.getString(context.getString(R.string.caption_user_set_key), null); final int textRendererIndex = getCaptionRendererIndex(); - if (userPreferredLanguage != null - && !selectedPreferredLanguages.contains(userPreferredLanguage) + if (userPreferredLanguage == null) { + trackSelector.setParameters(trackSelector.buildUponParameters() + .setRendererDisabled(textRendererIndex, true)); + return; + } + + final List selectedPreferredLanguages = + trackSelector.getParameters().preferredTextLanguages; + if (!selectedPreferredLanguages.contains(userPreferredLanguage) && textRendererIndex != RENDERER_UNAVAILABLE) { trackSelector.setParameters(trackSelector.buildUponParameters() .setPreferredTextLanguages(userPreferredLanguage, From 29fc0eff38b7480a1dcebb140302512fe5a0ccab Mon Sep 17 00:00:00 2001 From: karyogamy Date: Sun, 17 Apr 2022 18:34:31 -0400 Subject: [PATCH 077/992] fixed: added comments for DefaultTrackSelector auto-select fix. --- .../java/org/schabi/newpipe/player/Player.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 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 9f95aa519..30c62af39 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -3557,20 +3557,27 @@ public final class Player implements } // apply caption language from previous user preference + final int textRendererIndex = getCaptionRendererIndex(); + if (textRendererIndex == RENDERER_UNAVAILABLE) { + return; + } + + // If user prefers to show no caption, then disable the renderer. + // Otherwise, DefaultTrackSelector may automatically find an available caption + // and display that. final String userPreferredLanguage = prefs.getString(context.getString(R.string.caption_user_set_key), null); - final int textRendererIndex = getCaptionRendererIndex(); - if (userPreferredLanguage == null) { trackSelector.setParameters(trackSelector.buildUponParameters() .setRendererDisabled(textRendererIndex, true)); return; } + // Only set preferred language if it does not match the user preference, + // otherwise there might be an infinite cycle at onTextTracksChanged. final List selectedPreferredLanguages = trackSelector.getParameters().preferredTextLanguages; - if (!selectedPreferredLanguages.contains(userPreferredLanguage) - && textRendererIndex != RENDERER_UNAVAILABLE) { + if (!selectedPreferredLanguages.contains(userPreferredLanguage)) { trackSelector.setParameters(trackSelector.buildUponParameters() .setPreferredTextLanguages(userPreferredLanguage, PlayerHelper.captionLanguageStemOf(userPreferredLanguage)) From 2b8eb7ed66f0178ba8fc84c44981dde8f01574c3 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Mon, 18 Apr 2022 14:28:56 +0200 Subject: [PATCH 078/992] Also run CI when target is release branch --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13a4d8723..306b8c2c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: branches: - dev - master + - release/** paths-ignore: - 'README.md' - 'doc/**' From 127a27315e3c813d0d323f3471b4b51fcd94ee3f Mon Sep 17 00:00:00 2001 From: Yingwei Zheng Date: Mon, 18 Apr 2022 22:05:42 +0800 Subject: [PATCH 079/992] Fix keyboard showing after the search box acquiring focus (#8227) * Fix keyboard showing after the search box acquiring focus * Fix the underlying problem as described in the issue #7647 --- .../java/org/schabi/newpipe/util/KeyboardUtil.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/KeyboardUtil.java b/app/src/main/java/org/schabi/newpipe/util/KeyboardUtil.java index 71c0d3944..a709dc32e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/KeyboardUtil.java +++ b/app/src/main/java/org/schabi/newpipe/util/KeyboardUtil.java @@ -24,7 +24,19 @@ public final class KeyboardUtil { if (editText.requestFocus()) { final InputMethodManager imm = ContextCompat.getSystemService(activity, InputMethodManager.class); - imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED); + if (!imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED)) { + /* + * Sometimes the keyboard can't be shown because Android's ImeFocusController is in + * a incorrect state e.g. when animations are disabled or the unfocus event of the + * previous view arrives in the wrong moment (see #7647 for details). + * The invalid state can be fixed by to re-focusing the editText. + */ + editText.clearFocus(); + editText.requestFocus(); + + // Try again + imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED); + } } } From ec5cfe001903d8790015648d75ef226bb1eadbe0 Mon Sep 17 00:00:00 2001 From: Nickoriginal <85299944+Nickoriginal@users.noreply.github.com> Date: Wed, 20 Apr 2022 16:15:27 +0300 Subject: [PATCH 080/992] Update USER_AGENT in DownloaderImpl --- app/src/main/java/org/schabi/newpipe/DownloaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index fde991655..1a3a8adee 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -43,7 +43,7 @@ import static org.schabi.newpipe.MainActivity.DEBUG; public final class DownloaderImpl extends Downloader { public static final String USER_AGENT - = "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"; + = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY = "youtube_restricted_mode_key"; public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000"; From d5a0f8f23c05beec0b284d0c8fe27d1d002f1f48 Mon Sep 17 00:00:00 2001 From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com> Date: Thu, 21 Apr 2022 19:53:35 +0200 Subject: [PATCH 081/992] Set opacity of the popup close button to 0.8 on Android 12 and higher Setting this opacity should allow touches outside NewPipe when using the popup player. See https://developer.android.com/reference/android/view/WindowManager.LayoutParams#FLAG_NOT_TOUCHABLE for more details. --- .../newpipe/player/helper/PlayerHelper.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 6a7c27bdc..eab6ccc3e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -78,6 +78,20 @@ public final class PlayerHelper { private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x"); private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%"); + /** + * Maximum opacity allowed for Android 12 and higher to allow touches on other apps when using + * NewPipe's popup player. + * + *

+ * This value is hardcoded instead of being get dynamically with the method linked of the + * constant documentation below, because it is not static and popup player layout parameters + * are generated with static methods. + *

+ * + * @see WindowManager.LayoutParams#FLAG_NOT_TOUCHABLE + */ + private static final float MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER = 0.8f; + @Retention(SOURCE) @IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI, AUTOPLAY_TYPE_NEVER}) @@ -572,6 +586,12 @@ public final class PlayerHelper { flags, PixelFormat.TRANSLUCENT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // Setting maximum opacity allowed for touch events to other apps for Android 12 and + // higher to prevent non interaction when using other apps with the popup player + closeOverlayLayoutParams.alpha = MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER; + } + closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; closeOverlayLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; From 31e762d921f3dd4e02143b79fd122a86b228d316 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 28 Apr 2022 11:09:04 +0200 Subject: [PATCH 082/992] Update NewPipeExtractor to 0.22.1 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e93b2aaf4..19c491a4d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:b77c72fb8826c3ffca0be5f96b066cca0a07b1c9' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:ac1c22d81c65b7b0c5427f4e1989f5256d617f32' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" From 9a5decdb2834651b49b2dc95c8d9f758a3721f20 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 27 Apr 2022 21:02:14 +0200 Subject: [PATCH 083/992] Translated using Weblate (Bengali (India)) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 45.6% (289 of 633 strings) Translated using Weblate (Danish) Currently translated at 47.2% (299 of 633 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Basque) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Tagalog) Currently translated at 9.4% (60 of 633 strings) Translated using Weblate (Arabic (Libya)) Currently translated at 5.9% (4 of 67 strings) Translated using Weblate (Norwegian Bokmål) Currently translated at 14.9% (10 of 67 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (67 of 67 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 61.1% (41 of 67 strings) Translated using Weblate (Slovak) Currently translated at 7.4% (5 of 67 strings) Translated using Weblate (Persian) Currently translated at 62.6% (42 of 67 strings) Translated using Weblate (Swedish) Currently translated at 50.7% (34 of 67 strings) Translated using Weblate (Spanish) Currently translated at 59.7% (40 of 67 strings) Translated using Weblate (Indonesian) Currently translated at 79.1% (53 of 67 strings) Translated using Weblate (Polish) Currently translated at 55.2% (37 of 67 strings) Translated using Weblate (Hebrew) Currently translated at 55.2% (37 of 67 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (67 of 67 strings) Translated using Weblate (Russian) Currently translated at 17.9% (12 of 67 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 10.4% (7 of 67 strings) Translated using Weblate (Turkish) Currently translated at 28.3% (19 of 67 strings) Translated using Weblate (German) Currently translated at 65.6% (44 of 67 strings) Translated using Weblate (Sardinian) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Filipino) Currently translated at 18.7% (119 of 633 strings) Translated using Weblate (Estonian) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 97.6% (618 of 633 strings) Translated using Weblate (Persian) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Polish) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Slovak) Currently translated at 98.5% (624 of 633 strings) Translated using Weblate (Greek) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 90.0% (570 of 633 strings) Translated using Weblate (Basque) Currently translated at 95.7% (606 of 633 strings) Translated using Weblate (Italian) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Korean) Currently translated at 71.8% (455 of 633 strings) Translated using Weblate (Japanese) Currently translated at 99.5% (630 of 633 strings) Translated using Weblate (Russian) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (French) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (French) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Spanish) Currently translated at 97.6% (618 of 633 strings) Translated using Weblate (German) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (German) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (German) Currently translated at 100.0% (633 of 633 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 99.8% (632 of 633 strings) Translated using Weblate (German) Currently translated at 98.1% (621 of 633 strings) Translated using Weblate (German) Currently translated at 98.1% (621 of 633 strings) Translated using Weblate (Swedish) Currently translated at 99.6% (631 of 633 strings) Translated using Weblate (Swedish) Currently translated at 99.6% (631 of 633 strings) Translated using Weblate (French) Currently translated at 99.5% (630 of 633 strings) Translated using Weblate (French) Currently translated at 99.5% (630 of 633 strings) Translated using Weblate (French) Currently translated at 99.3% (629 of 633 strings) Translated using Weblate (French) Currently translated at 99.3% (629 of 633 strings) Co-authored-by: Agnieszka C Co-authored-by: AioiLight Co-authored-by: Ajeje Brazorf Co-authored-by: Alberto De Negri Co-authored-by: Alex25820 Co-authored-by: Allan Nordhøy Co-authored-by: Andrés Paredes Co-authored-by: Ayoub Rejal Co-authored-by: BurningKarl Co-authored-by: Danial Behzadi Co-authored-by: DanieLoche Co-authored-by: DanieLoche Co-authored-by: David Kovács Co-authored-by: Deleted User Co-authored-by: Digiwizkid Co-authored-by: Gontzal Manuel Pujana Onaindia Co-authored-by: Hosted Weblate Co-authored-by: Ihor Hordiichuk Co-authored-by: JS Ahn Co-authored-by: Jeff Huang Co-authored-by: Jonatan Nyberg Co-authored-by: Jonathan Soares Co-authored-by: Karl Tammik Co-authored-by: Lars Co-authored-by: Linerly Co-authored-by: Napstaguy04 Co-authored-by: Oğuz Ersen Co-authored-by: Ray Co-authored-by: Retrial Co-authored-by: Rex_sa Co-authored-by: Ricardo Co-authored-by: Simon N Co-authored-by: TiA4f8R Co-authored-by: VfBFan Co-authored-by: Yaron Shahrabani Co-authored-by: chr56 Co-authored-by: jazzyjabroni Co-authored-by: nautilusx Co-authored-by: nzgha Co-authored-by: qqqq1 Co-authored-by: sal0max Co-authored-by: ssantos Co-authored-by: Егор Ермаков Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ar_LY/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/es/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fa/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/id/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/nb_NO/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sv/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/ Translation: NewPipe/Metadata --- app/src/main/res/values-ar/strings.xml | 26 ++++++++++++ .../main/res/values-b+zh+HANS+CN/strings.xml | 22 ++++++++++ app/src/main/res/values-bn-rIN/strings.xml | 16 +++++-- app/src/main/res/values-da/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 24 ++++++++++- app/src/main/res/values-el/strings.xml | 22 ++++++++++ app/src/main/res/values-es/strings.xml | 4 ++ app/src/main/res/values-et/strings.xml | 24 +++++++++++ app/src/main/res/values-eu/strings.xml | 31 +++++++++++++- app/src/main/res/values-fa/strings.xml | 42 ++++++++++++++----- app/src/main/res/values-fil/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 22 ++++++++++ app/src/main/res/values-he/strings.xml | 26 +++++++++++- app/src/main/res/values-in/strings.xml | 21 ++++++++++ app/src/main/res/values-it/strings.xml | 24 ++++++++++- app/src/main/res/values-ja/strings.xml | 21 ++++++++++ app/src/main/res/values-ko/strings.xml | 41 ++++++++++++------ app/src/main/res/values-pl/strings.xml | 30 +++++++++++-- app/src/main/res/values-pt-rBR/strings.xml | 22 ++++++++++ app/src/main/res/values-pt-rPT/strings.xml | 22 ++++++++++ app/src/main/res/values-pt/strings.xml | 22 ++++++++++ app/src/main/res/values-ru/strings.xml | 5 +++ app/src/main/res/values-sc/strings.xml | 24 ++++++++++- app/src/main/res/values-sk/strings.xml | 10 +++++ app/src/main/res/values-sv/strings.xml | 32 +++++++++++--- app/src/main/res/values-tl/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 22 ++++++++++ app/src/main/res/values-uk/strings.xml | 23 ++++++++++ app/src/main/res/values-zh-rHK/strings.xml | 25 ++++++++++- app/src/main/res/values-zh-rTW/strings.xml | 21 ++++++++++ .../metadata/android/ar_LY/changelogs/63.txt | 7 ++++ .../metadata/android/ar_LY/changelogs/64.txt | 7 ++++ .../android/ar_LY/full_description.txt | 1 + .../android/ar_LY/short_description.txt | 1 + .../metadata/android/de/changelogs/985.txt | 2 +- .../metadata/android/es/changelogs/66.txt | 1 + .../metadata/android/es/changelogs/68.txt | 8 ++++ .../metadata/android/es/changelogs/71.txt | 6 +++ .../metadata/android/es/changelogs/730.txt | 2 + .../metadata/android/es/changelogs/770.txt | 2 +- .../metadata/android/fa/changelogs/850.txt | 1 + .../metadata/android/fa/changelogs/982.txt | 1 + .../metadata/android/fa/changelogs/985.txt | 1 + .../metadata/android/he/changelogs/986.txt | 16 +++++++ .../metadata/android/id/changelogs/986.txt | 16 +++++++ .../metadata/android/nb-NO/changelogs/986.txt | 16 +++++++ .../metadata/android/pl/changelogs/986.txt | 16 +++++++ .../metadata/android/ru/changelogs/982.txt | 1 + .../metadata/android/sk/changelogs/64.txt | 8 ++++ .../metadata/android/sv/changelogs/969.txt | 13 ++++++ .../metadata/android/tr/changelogs/910.txt | 1 + .../metadata/android/tr/changelogs/963.txt | 1 + .../metadata/android/tr/changelogs/982.txt | 1 + .../metadata/android/tr/changelogs/985.txt | 1 + .../metadata/android/uk/changelogs/985.txt | 2 +- .../metadata/android/uk/changelogs/986.txt | 13 ++++++ .../android/zh-Hans/changelogs/986.txt | 16 +++++++ .../android/zh-Hant/changelogs/985.txt | 1 + .../android/zh-Hant/changelogs/986.txt | 16 +++++++ .../android/zh_Hant_HK/changelogs/985.txt | 1 + .../android/zh_Hant_HK/changelogs/986.txt | 16 +++++++ .../android/zh_Hant_HK/full_description.txt | 2 +- .../android/zh_Hant_HK/short_description.txt | 2 +- 63 files changed, 778 insertions(+), 48 deletions(-) create mode 100644 fastlane/metadata/android/ar_LY/changelogs/63.txt create mode 100644 fastlane/metadata/android/ar_LY/changelogs/64.txt create mode 100644 fastlane/metadata/android/ar_LY/full_description.txt create mode 100644 fastlane/metadata/android/ar_LY/short_description.txt create mode 100644 fastlane/metadata/android/es/changelogs/66.txt create mode 100644 fastlane/metadata/android/es/changelogs/68.txt create mode 100644 fastlane/metadata/android/es/changelogs/71.txt create mode 100644 fastlane/metadata/android/es/changelogs/730.txt create mode 100644 fastlane/metadata/android/fa/changelogs/850.txt create mode 100644 fastlane/metadata/android/fa/changelogs/982.txt create mode 100644 fastlane/metadata/android/fa/changelogs/985.txt create mode 100644 fastlane/metadata/android/he/changelogs/986.txt create mode 100644 fastlane/metadata/android/id/changelogs/986.txt create mode 100644 fastlane/metadata/android/nb-NO/changelogs/986.txt create mode 100644 fastlane/metadata/android/pl/changelogs/986.txt create mode 100644 fastlane/metadata/android/ru/changelogs/982.txt create mode 100644 fastlane/metadata/android/sk/changelogs/64.txt create mode 100644 fastlane/metadata/android/sv/changelogs/969.txt create mode 100644 fastlane/metadata/android/tr/changelogs/910.txt create mode 100644 fastlane/metadata/android/tr/changelogs/963.txt create mode 100644 fastlane/metadata/android/tr/changelogs/982.txt create mode 100644 fastlane/metadata/android/tr/changelogs/985.txt create mode 100644 fastlane/metadata/android/uk/changelogs/986.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/986.txt create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/985.txt create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/986.txt create mode 100644 fastlane/metadata/android/zh_Hant_HK/changelogs/985.txt create mode 100644 fastlane/metadata/android/zh_Hant_HK/changelogs/986.txt diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 818f946dd..7022a2c29 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -737,4 +737,30 @@ خطوة الإيقاع الافتراضي ExoPlayer تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل. + تكوين إشعار مشغل البث الحالي + الإشعارات + تحميل تفاصيل البث… + تم تعطيل الإشعارات + بث جديد + إشعار المشغل + لقد اشتركت الآن في هذه القناة + الإخطارات حول التدفقات الجديدة للاشتراكات + إشعارات أحداث البث الجديدة + الإخطار بأحداث البث الجديدة من الاشتراكات + تشغيل التحقق من وجود تدفقات جديدة + معدل البحث + مطلوب اتصال الشبكة + أي شبكة + محو جميع الملفات التي تم تنزيلها من القرص؟ + الحصول على إشعار + , + تبديل الكل + + %s دفق جديد + %s دفق جديد + %s دفق جديد + %s دفوق جديدة + %s دفق جديد + %s دفق جديد + \ No newline at end of file diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index 4aa497e99..26c930886 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -677,4 +677,26 @@ 节奏步长 改变加载间隔的大小(当前%s),较低的值可以加快初始的视频加载速度,改变需要重启播放器。 ExoPlayer 默认 + 配置当前正在播放的串流的通知 + 新串流通知 + 检查频率 + 所需的网络连接 + 通知已被禁用 + 你刚刚订阅了此频道 + + 全选 + 播放器通知 + 通知 + 新的串流 + + %s 条新串流 + + + 被订阅的新串流的通知 + 正在加载串流详情… + 检查新串流 + 任何网络 + 清除所有下载的文件? + 获取通知 + 来自订阅的新串流的通知 \ No newline at end of file diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index 12d526a7d..24498f162 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -189,7 +189,7 @@ কোন রেজাল্ট নেই কোন ফোল্ডার নেই বাহ্যিক স্টোরেজ নেই - সার্চ ইতিহাস ডিলিট হয়েছে। + সার্চ ইতিহাস মোছা হয়েছে নাম নাম পরিবর্তন ইতিহাস @@ -198,7 +198,7 @@ তৈরি করুন পুনরায় চেষ্টা করুন সাহায্য - দেখার ইতিহাস মুছে গেছে। + দেখার ইতিহাস মুছে গেছে সম্পূর্ণ দেখার ইতিহাস মুছে ফেলুন\? দেখার ইতিহাস মুছে ফেলুন ডাটা বেস এক্সপোর্ট করুন @@ -269,14 +269,14 @@ তালিকা তে পজিশন শেষ প্লে ব্যাক পজিশন এ যান সার্চ গুলি স্থানীয় ভাবে জমা করুন - সার্চ এর সময় সাজেশন দেখান + সার্চ এর সময় সাজেশন পছন্দ করুন সার্চ সাজেশন ফিড লোড হচ্ছে… এরর দেখান একটি প্লে লিস্ট পছন্দ করুন সম্পর্কিত থার্ড-পার্টি লাইসেন্স সমূহ - গিটহাব এ এরর রিপোর্ট করুন + গিটহাব এ রিপোর্ট করুন ডিফল্ট এ ফিরে যান রেজাল্ট দেখান হচ্ছেঃ %s কিউ মোছার আগে নিশ্চিত করুন @@ -292,4 +292,12 @@ মেটা ইনফো দেখান বিবরণ দেখান রাত্রি থিম + তৃতীয় অ্যাকশান বোতাম + চতুর্থ অ্যাকশান বোতাম + সক্রিয় প্লেয়ার ক্রম পরিবর্তিত হয়ে যাবে + স্থানীয় সার্চ সাজেশন + পঞ্চম অ্যাকশান বোতাম + প্রথম অ্যাকশান বোতাম + দ্বিতীয় অ্যাকশান বোতাম + নতুন স্ট্রিম \ No newline at end of file diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 8fadebacd..71bb83fb0 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -396,4 +396,5 @@ Viser resultater for: %s Åben med LeakCanary er ikke tilgængelig + Markér som set \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index edde1ab27..51224727d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -666,9 +666,9 @@ Hauptplayer im Vollbildmodus starten Als Nächstes eingereiht Als Nächstes in Wiedergabe einreihen - Verarbeitung… Kann einen Moment dauern + Verarbeitung … Kann einen Moment dauern Nach Aktualisierungen suchen - Suche nach Aktualisierungen… + Suche nach Aktualisierungen … Manuelle Prüfung auf neue Versionen Neue Feed-Elemente \"Player abstürzen lassen\" anzeigen @@ -691,4 +691,24 @@ Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players. Geschwindigkeitsstufe ExoPlayer Standard + Benachrichtigungen + Benachrichtigen über neue abonnierbare Streams + Neue Streams + + %s neuer Stream + %s neue Streams + + Stream-Details laden … + Über neue Streams aus Abonnements benachrichtigen + , + Jedes Netzwerk + Benachrichtigungen über neue Streams + Suche nach neuen Streams ausführen + Erforderliche Netzwerkverbindung + Benachrichtigungen sind deaktiviert + Benachrichtigung erhalten + Alle heruntergeladenen Dateien von der Festplatte löschen\? + Du hast jetzt diesen Kanal abonniert + Alle umschalten + Aktualisierungsintervall \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 911c97be6..769292f58 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -689,4 +689,26 @@ Βήμα τέμπο Εξ\' ορισμού ExoPlayer Αλλάξτε το μέγεθος του διαστήματος φόρτωσης (επί του παρόντος είναι %s). Μια χαμηλότερη τιμή μπορεί να επιταχύνει την αρχική φόρτωση βίντεο. Οι αλλαγές απαιτούν επανεκκίνηση της εφαρμογής. + Ειδοποιήσεις + + %s νέα ροή + %s νέες ροές + + Απαιτούμενη σύνδεση δικτύου + Οποιοδήποτε δίκτυο + Ειδοποίηση αναπαραγωγής + Νέες ροές + Διαμόρφωση ειδοποίησης τρέχουσας αναπαραγωγής ροής + Συχνότητα ελέγχου + Ειδοποιήσεις σχετικά με νέες ροές για συνδρομές + Φόρτωση λεπτομερειών ροής… + Εκτελέστε έλεγχο για νέες ροές + Ειδοποιήσεις για νέες ροές + Ειδοποίηση για νέες ροές από συνδρομές + Διαγραφή όλων των ληφθέντων αρχείων από το δίσκο; + Οι ειδοποιήσεις είναι απενεργοποιημένες + , + Λάβετε ειδοποίηση + Έχετε εγγραφεί τώρα σε αυτό το κανάλι + Εναλλαγή όλων \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 4d69a096d..2bef8db08 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -692,4 +692,8 @@ Paso de tempo Cambia el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial del vídeo. Los cambios requieren un reinicio del reproductor. Ajustar el tono por semitonos musicales + Notificaciones + Nuevos streams + Notificación del reproductor + Configurar notificación de la reproducción en curso \ No newline at end of file diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 439c9d906..055f103c6 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -687,4 +687,28 @@ LeakCanary pole saadaval ExoPlayer\'i vaikimisi väärtused Muuda video laadimise välpa (hetkel %s). Väiksemast väärtusest võib abi olla, kui tahad et video esitamine algaks varem. Muudatuste jõustamine eeldab rakenduse uuesti käivitamist. + Meediamängija teavitused + Teavitused pole kasutusel + Kontrollimise sagedus + Tellimuste-kohaste uute meedivoogude teavitused + Kas kustutame kõik allalaaditud failid andmekandjalt\? + Teavitused + Seadista hetkel esitatava meediavoo teavitusi + Uued meediavood + + %s uus meediavoog + %s uut meediavoogu + + Laadin meediavoo teavet… + Käivita uute meediavoogude kontroll + Uute meedivoogude teavitused + Tellimustega seotud uute meedivoogude teavitused + Võrguühendus on vajalik + Mis tahes võrk + Saa teavitusi + Sa oled nüüd selle kanali tellija + , + Lülita kõik sisse + Reguleeri helikõrgust muusikaliste pooltoonide kaupa + Tempo samm \ No newline at end of file diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 004d5d7a8..7faed5800 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -315,7 +315,7 @@ NewPipe proiektuak aintzat hartzen du zure pribatutasuna. Aplikazioak ez du zure baimenik gabe daturik jasotzen. \nNewPipe pribatutasun politikak azaltzen du zehazki bidali eta gordetako informazioa zein den kraskatze txosten bat bidaltzen duzunean. Irakurri pribatutasun politika - European General Data Protection Regulation (GDPR) legea betetzeko, NewPipe pribatutasun politika irakurtzera gonbidaatzen zaitugu. + Datuak Babesteko Araudi Orokorra (GDPR) betetzeko, NewPipe-en pribatutasun politika kontuan hartzera gonbidatzen zaitugu. Mesedez, irakurri kontu handiz. \nAkats txosten bat bidali ahal izateko onartu behar duzu. Onartu Ukatu @@ -664,7 +664,7 @@ Hasi erreproduzitzaile nagusia pantaila osoan Isatsari bideo hau erantsita Gehitu bideo hau isatsari - Erakutsi \"itxi erreproduzigailua\" + Erakutsi \"Itxi erreproduzigailua\" Prozesatzen... Itxoin mesedez Atzeko planoan erreproduzitzen dagoeneko Erroreen txostenen jakinarazpena @@ -684,4 +684,31 @@ Erreproduzigailua erabiltzean ustekabean ixteko aukera ematen du Erakutsi errore barra bat Sortu errore jakinarazpen bat + Konfiguratu uneko erreprodukzioaren jakinarazpenak + Jakinarazpenak + Erreproduzigailuaren jakinarazpenak + Jario berriak + Egiaztatu jario berriak + Jario berrien jakinarazpenak + Edozein sare + Jakinarazpenak desgaituta daude + Kanal honetara harpidetu zara + , + Txandakatu denak + Doitu tonua semitono musikalen arabera + Tempo urratsa + Aldatu karga maiztasun tamaina (unean %s). Balio txikiago batek bideoaren hasierako karga azkartu dezake. Erreproduzigailuaren berrabiarazte bat behar du. + Harpidetzen jario berriei buruz jakinarazi + Ezabatu deskargatutako fitxategi guztiak biltegitik\? + Harpidetzentzako jario berrien jakinarazpenak + Jarioaren xehetasunak kargatzen… + + jario berri %s + %s jario berri + + LeakCanary ez dago eskuragarri + Egiaztapen maiztasuna + Jakinarazi + ExoPlayer lehenetsia + Beharrezko sare konexioa \ No newline at end of file diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 7d139d347..3186deb72 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -154,17 +154,17 @@ M B - %s مشترک + ۱ مشترک %s مشترک بدون بازدید - %s بازدید + ۱ بازدید %s بازدید بدون ویدیو - %s ویدیو + ۱ ویدیو %s ویدیو ایجاد @@ -438,7 +438,7 @@ جدید می‌خواهید این گروه را پاک کنید؟ - %d مورد گزیده + ۱ مورد گزیده %d مورد گزیده پردازش خوراک… @@ -446,19 +446,19 @@ بارگیری نشده: %d آخرین به‌روزرسانی خوراک: %s - %d روز + ۱ روز %d روز - %d ساعت + ۱ ساعت %d ساعت - %d دقیقه + ۱ دقیقه %d دقیقه - %d ثانیه + ۱ ثانیه %d ثانیه بله، و ویدیوهای ناقص دیده شده @@ -485,12 +485,12 @@ ∞ ویدیو بیش از ۱۰۰ ویدیو - %s شنونده + ۱ شنونده %s شنونده کسی در حال شنیدن نیست - %s بیننده + ۱ بیننده %s بیننده کسی در حال مشاهده نیست @@ -689,4 +689,26 @@ تنظیم زیر و بم با شبه‌تن‌ها تغییر اندازهٔ بازهٔ بار (هم‌اکنون %s). مقداری پایین‌تر، می‌تواند بار کردن نخستین ویدیو را سرعت بخشد. تغییرها نیاز به یک آغاز دوبارهٔ پخش‌کننده دارند. پیش‌گزیدهٔ اگزوپلیر + آگاهی‌ها + بار کردن جزییات جریان… + اجرای بررسی برای جریان‌های جدید + نیازمند اتّصال شبکه + ، + تغییر وضعیت همه + جریان‌های جدید + + ۱ جریان جدید + %s جریان جدید + + پیکربندی آگاهی جریان در حال پخش کنونی + آگاهی‌های جریان‌های جدید + آگاهی پخش‌کننده + آگاهی‌ها دربارهٔ جریان‌های جدید برای اشتراک‌ها + آگاهی‌ها از کار افتاده‌اند + آگاه شوید + بسامد بررسی + آگاه کردن دربارهٔ جریان‌های جدید از اشتراک‌ها + پاک کردن تمامی پرونده‌های بارگرفته از دیسک؟ + هر شبکه‌ای + اکنون مشترک این کانال شده‌اید \ No newline at end of file diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index c0197c3cf..1795fc98c 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -117,4 +117,5 @@ Haba ng fast forward/-rewind seek Ituloy ang pagpapalabas Mga Patok Ngayon + Subaybayan ang mga napanood nang video \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a90822091..9b103256d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -691,4 +691,26 @@ Règler la hauteur par demi-tons musicaux Pas du tempo Valeur par défaut d’ExoPlayer + Nouveaux flux + Configurer la notification du flux en cours de lecture + Recevoir des notifications + Chargement des détails du flux… + + %s nouveau flux + %s nouveaux flux + + Connexion réseau requise + Notifications + Notifications désactivées + Vous êtes maintenant abonné(e) à cette chaîne + Notification du Lecteur + Notifications pour de nouveaux flux des abonnements + Supprimer tous les fichiers téléchargés du disque \? + Exécuter la vérification de nouveaux flux + Notifications pour de nouveaux flux + N\'importe quel réseau + Fréquence de vérification + Notifications pour de nouveaux flux des abonnements + , + Sélectionner/Désélectionner tout \ 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 90c85c483..f6dd702b4 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -565,7 +565,7 @@ להתחיל לנגן אוטומטית — %s לנגן את התור לא ניתן לזהות את הכתובת. לפתוח אותה ביישומון אחר\? - הוספה אוטומטית לתור + סידור אוטומטי בתור התור מהנגן הפעיל יוחלף מעבר מנגן אחד למשנהו עלול להחליף את התור שלך לבקש אישור לפני מחיקת התור @@ -713,4 +713,28 @@ צעד מקצב ברירת מחדל של ExoPlayer שינוי גודל מרווח הטעינה (כרגע %s). ערך נמוך יותר עשוי להאיץ את טעינת הווידאו הראשונית. שינויים דורשים את הפעלת הנגן מחדש. + התראות על תזרימים חדשים להרשמה + תדירות בדיקה + נדרש חיבור לרשת + קבלת הודעה + להחליף את המצב של הכול + הגדרת התראת התזרים שמתנגן כרגע + התראות + + תזרים חדש + %s תזרימים חדשים + %s תזרימים חדשים + %s תזרימים חדשים + + פרטי התזרים נטענים… + הרצת בדיקה על תזרימים חדשים + התראות תזרימים חדשים + הודעה על תזרימים חדשים מהמינויים + , + תזרימים חדשים + התראות נגן + רשת כלשהי + למחוק את כל הקבצים שהורדו מהכונן\? + התראות מושבתות + נרשמת לערוץ הזה \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index bf02517ac..83a4a147c 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -677,4 +677,25 @@ Default ExoPlayer Atur nada berdasarkan semitone musik Ubah ukuran interval pemuatan (saat ini %s). Sebuah nilai yang rendah mungkin dapat membuat pemuatan video awal lebih cepat. Membutuhkan sebuah pemulaian ulang pada pemain. + Memuat detail stream… + Frekuensi pemeriksaan + Dibutuhkan koneksi jaringan + Jaringan apa saja + Beritahu saya + , + Alih semua + + %s stream baru + + Jalankan periksa untuk stream baru + Hapus semua file yang terunduh dari disk\? + Notifikasi dinonaktifkan + Notifikasi pemain + Notifikasi + Konfigurasi notifikasi permainan stream saat ini + Stream baru + Notifikasi tentang stream baru untuk langganan + Notifikasi stream baru + Beritahu tentang stream baru dari notifikasi + Anda sekarang berlangganan ke channel ini \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9ab26ae6d..6e1b213e4 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -291,7 +291,7 @@ Elimina i dati delle pagine web memorizzati nella cache Cache metadati svuotata Controlli della velocità di riproduzione - Tempo + Velocità Tono Scollega (può causare distorsione) Nessun flusso disponibile per il download @@ -689,4 +689,26 @@ Predefinito ExoPlayer Cambia la dimensione dell\'intervallo da caricare (attualmente %s). Un valore basso può velocizzare il caricamento iniziale del video. La modifica richiede il riavvio del lettore. Passo tempo + Notifiche di nuove stream dalle iscrizioni + Frequenza controllo + Richiesta connessione alla rete + Ricevi le notifiche + Sei ora iscritto a questo canale + , + Attiva/disattiva tutti + Notifiche per nuove stream + Notifica lettore + Configura la notifica della stream attualmente in riproduzione + Notifiche + Nuove stream + + %s nuova stream + %s nuove stream + + Caricando i dettagli della stream… + Cancellare tutti i file scaricati dal dispositivo\? + Avvia controllo per nuove stream + Qualsiasi rete + Le notifiche sono disabilitate + Notifica se ci sono nuove stream dalle iscrizioni \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 2bb071f8e..9469d81e0 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -674,4 +674,25 @@ エラーを報告する通知 LeakCanaryが利用不可能です 緩急音階 + プレイヤー通知 + ストリームの詳細を読み込んでいます… + 登録チャンネルの新しいストリームについて通知する + , + 通知は無効化されています + ExoPlayer のデフォルト + 通知を受け取る + このチャンネルを購読しました + ディスクからダウンロードしたすべてのファイルを削除しますか? + すべて切り替え + 確認する頻度 + 新しいストリームのチェックを実行 + 購読チャンネルの新しいストリームに関する通知 + 新しいストリームの通知 + + %s 件の新しいストリーム + + 新しいストリーム + 通知 + 現在再生しているストリームの通知を構成 + 読み込む間隔を変更します (現在 %s)。小さい値にすると初回読み込み時間が短くなります。変更にはプレイヤーの再起動が必要です。 \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index b163640b2..d0d54c62d 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -25,7 +25,7 @@ 다음/유사한 비디오 표시 지원하지 않는 URL입니다 기본 컨텐츠 언어 - 비디오 & 오디오 + 비디오 및 오디오 비디오 재생, 구간: 업로더 썸네일 싫어요 @@ -43,7 +43,7 @@ 네트워크 오류 \"검색\" 버튼을 눌러서 시작하세요 컨텐츠 - 연령 제한 컨텐츠 + 연령 제한 컨텐츠 보여주기 라이브 오류 모든 썸네일을 불러올 수 없습니다 @@ -52,7 +52,7 @@ 컨텐츠를 사용할 수 없습니다 다운로드 메뉴를 설정할 수 없습니다 죄송합니다. 오류가 발생했습니다. - 이메일을 통해 오류 보고 + 이메일을 통해 이 오류 보고 죄송합니다. 오류가 발생했습니다. 보고 정보: @@ -81,7 +81,7 @@ 팝업 기본 팝업 해상도 높은 해상도 표시 - 일부 기기에서만 2K/4K 해상도 재생이 지원됩니다 + 일부 기기에서만 2K/4K 영상을 재생할 수 있습니다 기본 비디오 형식 검은 테마 팝업 크기 및 위치 기억 @@ -98,7 +98,7 @@ 비디오 상세 정보 페이지에서 백그라운드/팝업 재생 버튼을 누를 경우 팁을 표시합니다 플레이어 동작 - 기록 & 캐시 + 기록 및 캐시 팝업 모드에서 재생 중 오류 보고 전부 @@ -108,13 +108,13 @@ 되돌리기 전부 재생 NewPipe 알림 - NewPipe 백그라운드 및 팝업 플레이어 알림 + NewPipe 플레이어 알림 [알 수 없음] 앱/UI 충돌 이 스트림을 재생할 수 없습니다 복구할 수 없는 플레이어 오류가 발생했습니다 플레이어 오류로부터 복구 중 - 무엇을:\\n요청:\\n컨텐츠 언어:\\n서비스:\\nGMT 기준 시간:\\n패키지:\\n버전:\\n안드로이드 버전: + 무엇을:\\n요청:\\n컨텐츠 언어:\\컨텐츠 국가:\\n앱 언어:\\n서비스:\\nGMT 기준 시간:\\n패키지:\\n버전:\\n안드로이드 버전: 결과 없음 구독할 항목을 추가하세요 @@ -188,7 +188,7 @@ 재생목록 북마크 이곳에 추가 정확하지는 않지만 빠른 탐색 - 정확하지 않은 탐색은 더 빠르게 위치를 탐색할 수 있지만 정확도는 떨어집니다 + 정확하지 않은 탐색은 더 빠르게 위치를 탐색할 수 있지만 정확도는 떨어집니다. 5, 15, 25초 탐색은 이 기능과 같이 작동하지 않습니다 다음 스트림을 자동으로 대기열에 추가하기 이전 스트림이 반복 재생 대기열이 아닐 경우, 관련 스트림을 자동 재생합니다 기본 콘텐츠 국가 @@ -201,7 +201,7 @@ 데이터베이스 가져오기 데이터베이스 내보내기 현재 시청 기록 및 구독 목록을 덮어쓰기 합니다 - 시청 기록, 구독 목록과 재생목록을 내보냅니다 + 시청 기록과 구독 목록, 재생 목록, 설정을 내보냅니다 외부 플레이어는 이러한 종류의 링크를 지원하지 않습니다 발견된 비디오 스트림 없음 발견된 오디오 스트림 없음 @@ -300,11 +300,11 @@ 시청 기록 삭제하기 동영상 시청 기록과 저장된 재생 위치를 삭제합니다 모든 시청 기록을 삭제하시겠습니까\? - 동영상 시청 기록이 삭제되었습니다. + 동영상 시청 기록이 삭제되었습니다 검색 기록 삭제 검색 기록을 모두 삭제합니다 모든 검색 기록을 삭제하시겠습니까\? - 검색 기록이 삭제되었습니다. + 검색 기록이 삭제되었습니다 NewPipe 개인정보 보호 정책 NewPipe 프로젝트는 사용자의 개인 정보 보호를 최우선으로 생각하며, 동의 없이 어떠한 정보도 수집하지 않습니다. \nNewPipe 개인정보 보호 정책에서는 오류 보고 시 어떠한 정보가 수집되고 저장되는지 자세히 명시되어 있습니다. @@ -393,7 +393,7 @@ 리스트 내 위치 표시 리스트에서 재생 위치를 표시합니다 데이터 삭제 - 재생위치 삭제완료. + 재생위치 삭제완료 파일이 이동되거나 삭제되었습니다 파일을 덮어쓰기할 수 없습니다 해당 이름으로 대기된 다운로드가 있습니다 @@ -431,7 +431,7 @@ 빠른-감기/되감기 찾는 시간 피어튜브 인스턴스 당신이 선호하는 피어튜브 인스턴스를 선택하세요. - %s에서 당신에게 가장 잘 어울리는 인스턴스를 찾으세요. + %s에서 당신이 좋아하는 인스턴스를 찾으세요. 인스턴스 추가하기 인스턴스 URL을 입력하세요. 인스턴스를 검증할 수 없습니다. @@ -517,4 +517,19 @@ 어두운 테마 최대 3개까지 축소 알림에 표시될 항목을 고를 수 있습니다! 아래 항목을 터치해서 편집하세요. 위에서부터 체크된 3개 항목은 축소 알림일 때도 표시됩니다 + 유튜브의 \"제한 모드\"를 활성화 + 비디오 해싱 진행 알림 + 새로운 스트림 + 구독에서 새 스트림이 있을 때 알림 + 오류 보고 알림 + reCAPTCHA 쿠키를 비웠습니다 + 플레이어 알림 + 알림 + 유튜브는 잔인할 수 있는 컨텐츠를 숨겨주는 \"제한 모드\"를 제공합니다 + reCAPTCHA 쿠키 비우기 + 플레이어 크래시 발생 + 없음 + 전체화면으로 주 플레이어 시작 + 비디오 해시 알림 + GitHub에 보고 \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index d755cbcaf..a13dec423 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -22,7 +22,7 @@ Wybierz folder zapisywania plików audio Domyślna rozdzielczość Odtwórz w Kodi - Zainstalować brakującą aplikację Kore\? + Czy zainstalować brakującą aplikację Kore\? Pokazuj opcję „Odtwórz z Kodi” Wyświetlaj opcję odtwarzania wideo przez centrum multimedialne Kodi Dźwięk @@ -406,7 +406,7 @@ Wyczyść dane Usunięto pozycje odtwarzania Plik usunięty albo przeniesiony - Plik z tą nazwą już istnieje + Plik o tej nazwie już istnieje Nie udało się nadpisać pliku Plik o tej samej nazwie oczekuje na pobranie NewPipe został zamknięty podczas pracy nad plikiem @@ -539,7 +539,7 @@ Tak, i częściowo obejrzane wideo Wideo, które zostały obejrzane przed i po dodaniu do playlisty, zostaną usunięte. \nCzy na pewno\? Tego nie da się cofnąć! - Usunąć obejrzane wideo\? + Czy usunąć obejrzane wideo\? Usuń obejrzane Oryginalne teksty z usług będą widoczne w strumieniowanych pozycjach Pokazuj oryginalny czas na pozycjach @@ -709,4 +709,28 @@ domyślny ExoPlayera Dostosuj wysokość półtonami Krok tempa + Powiadomienie odtwarzacza + Skonfiguruj powiadomienie aktualnie odtwarzanego strumienia + Uruchom sprawdzenie nowych strumieni + Dowolna sieć + Subskrybujesz teraz ten kanał + Powiadomienia + Nowe strumienie + Powiadomienia o nowych strumieniach dla subskrypcji + + %s nowy strumień + %s nowe strumienie + %s nowych strumieni + %s nowych strumieni + + Ładowanie szczegółów strumienia… + Powiadomienia o nowych strumieniach + Powiadamiaj o nowych strumieniach z subskrypcji + Częstotliwość sprawdzania + Wymagane połączenie sieciowe + Czy usunąć wszystkie pobrane pliki z dysku\? + Powiadomienia są wyłączone + Otrzymuj powiadomienia + , + Przełącz wszystkie \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 408d33e87..1746d6615 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -689,4 +689,26 @@ Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. As alterações exigem que o player reinicie. Ajustar o tom por semitons musicais ExoPlayer padrão + Notificação do reprodutor + Configurar a notificação do fluxo da reprodução atual + Notificações + Novos streams + Notificações sobre novas transmissões para inscrições + Notificações de novas transmissões + Notificar sobre novas transmissões de inscrições + Frequência de verificação + Nenhuma rede + Excluir todos os arquivos baixados do disco\? + Agora você se inscreveu neste canal + Alternar tudo + , + Carregando detalhes da transmissão… + + %s nova transmissão + %s novas transmissões + + Executar verificação de novas transmissões + Conexão de rede necessária + As notificações estão desativadas + Seja notificado \ No newline at end of file diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index a9d43252b..8e2cd1366 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -689,4 +689,26 @@ Ajustar o tom por semitons musicais Passo do tempo Predefinido do ExoPlayer + Notificações + A carregar detalhes do fluxo… + Verificar se há novos fluxos + Notificações sobre novos fluxos + Frequência da verificação + Apagar todos os ficheiros descarregados do disco\? + Notificações são desativadas + + %s fluxo novo + %s fluxos novos + + Notificar sobre novos fluxos de assinaturas + Conexão de rede necessária + Qualquer rede + Alternar tudo + Notificações sobre novos fluxos para assinaturas + Notificação do reprodutor + Configurar a notificação da reprodução do fluxo atual + Seja notificado + Agora assinou este canal + , + Novos fluxos \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index f9b9856ee..d6993c012 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -690,4 +690,26 @@ Predefinido do ExoPlayer Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar. Passo do tempo + Notificação do reprodutor + Configurar a notificação da reprodução do fluxo atual + Notificações + A carregar detalhes do fluxo… + Verificar se há novos fluxos + Notificações sobre novos fluxos + Notificar sobre novos fluxos de assinaturas + Frequência da verificação + Conexão de rede necessária + Qualquer rede + Agora assinou este canal + Alternar tudo + Apagar todos os ficheiros descarregados do disco\? + Novos fluxos + Notificações sobre novos fluxos para assinaturas + + %s fluxo novo + %s fluxos novos + + Seja notificado + Notificações são desativadas + , \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 93ae31506..6527c8d36 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -725,4 +725,9 @@ Шаг темпа Стандартное значение ExoPlayer Изменить размер интервала загрузки (сейчас %s). Меньшее значение может ускорить начальную загрузку видео. Изменение значения потребует перезапуска плеера. + Загрузка деталей трансляции… + Проверить на наличие новых трансляций + Удалить все загруженные файлы\? + Уведомления плеера + , \ No newline at end of file diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 3fdd9b768..1a6456865 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -686,7 +686,29 @@ Giai in riprodutzione in s\'isfundu LeakCanary no est a disponimentu Règula s\'intonatzione in base a sos semitonos musicales - Passu de su ritmu + Passu de tempus Valore ExoPlayer predefinidu Muda sa mannària de s\'intervallu de carrigamentu (in custu momentu %s). Unu valore prus bassu diat pòdere allestrare su carrigamentu de incumintzu de su vìdeu. Sas modìficas tenent bisòngiu de torrare a allùghere su riproduidore. + Cunfigura sa notìfica de su flussu in cursu de riprodutzione + Notìficas de flussos noos dae sas iscritziones + + %s flussu nou + %s flussos noos + + Carrighende sos detàllios de su flussu… + Avia una verìfica pro flussos noos + Notìficas pro sos flussos noos + Notìfica de flussos noos dae sas iscritziones + Frecuèntzia de controllu + Connessione de retze rechesta + Cale si siat retze + Sas notìficas sunt disabilitadas + Retzi notìficas + , + Ativa/disativa totu + Como ses iscritu a custu canale + Notìfica de su riproduidore + Notìficas + Flussos noos + Iscantzellare totu sos archìvios iscarrigados dae su discu\? \ No newline at end of file diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 265dec114..c630eae16 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -702,4 +702,14 @@ Krok tempa ExoPlayer preddefinovaný Zmeniť interval načítania (aktuálne %s). Menšia hodnota môže zvýšiť rýchlosť prvotného načítania videa. Zmena vyžaduje reštart. + Upozornenia + Frekvencia kontroly + Vymazať všetky stiahnuté súbory z disku\? + Upozornenia sú vypnuté + Upozornenie z prehrávača + Nastavte notifikáciu aktuálneho prehrávania + Je vyžadované pripojenie na internet + Začali ste odoberať tento kanál + , + Zapnúť všetko \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index d08bdbf12..95d5ea82f 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -175,7 +175,7 @@ Välj en kanal Inga kanal prenumerationer ännu Välj en kiosk - Trend + Trendigt Topp 50 Nytt och populärt Ta bort @@ -243,7 +243,7 @@ Byt namn 1 objekt borttaget. Ingen app installerad för att spela upp filen - NewPipes Sekretesspolicy + NewPipes sekretesspolicy NewPipe-projektet tar din integritet på största allvar. Appen samlar därför inte in några uppgifter utan ditt medgivande. \nNewPipes sekretesspolicy förklarar i detalj vad för data som skickas och lagras när du skickar en kraschrapport. Läs sekretesspolicy @@ -258,7 +258,7 @@ Vill du också importera inställningar? Öppna navigationspanelen Stäng navigationspanelen - Föredragen \'öppna\' åtgärd + Föredragen \"öppna\" åtgärd Standardåtgärden när du öppnar innehåll — %s Videospelare Bakgrundsspelare @@ -524,7 +524,7 @@ Skapad av %s Ingen spellista har bokmärkts än Visa endast prenumerationer som inte grupperats - Skala tumnagel till 1:1-förhållande + Skala miniatyrbild till 1: 1 bildförhållande Endast över Wi-Fi Skala videominiatyrbilden som visas i aviseringen från 16:9- till 1:1-förhållande (kan orsaka bildförvrängning) Starta uppspelning automatiskt — %s @@ -637,7 +637,7 @@ Markera som sedd Ej listad Aktuellt - Nattema + Natt-tema Aviseringar för videohashningsframsteg Miniatyrbild-webbadress Inaktivera medietunnel om du upplever en svart skärm eller stamning vid videouppspelning @@ -689,4 +689,26 @@ ExoPlayer standard Ändra inläsningsintervallets storlek (för närvarande %s). Ett lägre värde kan påskynda den första videoinläsningen. Ändringar kräver omstart av spelaren. Temposteg + Validera frekvens + Kräver nätverksanslutning + Alla nätverk + Radera alla nedladdade filer från disken\? + Notifikationer är avstängda + Bli medelad + Du har nu prenumenerat till denna kanalen + Notifikationer om nya strömmar för prenumenanter + + %s Ny ström + %s Nya strömmar + + Konfigurera meddelande om aktuell ström som spelas upp + Kör leta efter nya strömmar + Medela om nya strömmar från prenumenanter + Notifikationer + Nya strömmar + Laddar strömdetaljer… + Nya strömmnings notifikationer + , + Spelaravisering + Växla alla \ No newline at end of file diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index 82e0feaac..d41b317e8 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -65,4 +65,5 @@ \n \nBuksan ang \"%1$s\" sa ayos ng app kung gusto mong makita ito. Mga Artista + Nakalutang \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 12a2d4348..4dd10063b 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -689,4 +689,26 @@ ExoPlayer öntanımlısı Tempo adımı Perdeyi müzikal yarım tonlarla uyarla + Yeni akış bildirimleri + Bildirimler + + %s yeni akış + %s yeni akış + + Yeni akışları denetlemeyi çalıştır + Oynatıcı bildirimi + Oynatılan akış bildirimini yapılandırın + Yeni akışlar + Akış ayrıntıları yükleniyor… + Abonelikler için yeni akışlarla ilgili bildirimler + Gerekli ağ bağlantısı + Bildirimler devre dışı + Bildirim alın + Artık bu kanala abone oldunuz + Aboneliklerden yeni akışlar hakkında bildirim gönder + Denetleme sıklığı + Herhangi bir ağ + İndirilen tüm dosyalar diskten silinsin mi\? + , + Tümünü değiştir \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 3f4d1e214..06e51f576 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -706,4 +706,27 @@ Регулювання висоти звуку за музичними півтонами Типовий ExoPlayer Змінити розмір інтервалу завантаження (наразі %s). Менше значення може прискорити початкове завантаження відео. Зміни вимагають перезапуску програвача. + Ви підписалися на цей канал + , + Сповіщення про нові трансляції для підписок + + %s нова трансляція + %s нові трансляції + %s нових трансляцій + %s нових трансляцій + + Завантаження відомостей про трансляцію… + Запустити виявлення нових трансляцій + Сповіщати про нові трансляції з підписок + Сповіщення вимкнено + Будь-яка мережа + Видалити всі завантажені файли з диска\? + Перемкнути всі + Сповіщати + Сповіщення програвача + Сповіщення + Нові трансляції + Сповіщення про нові трансляції + Частота перевірки + Необхідний тип з\'єднання \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 65054bebe..2a00a818e 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -145,7 +145,7 @@ 睇過嘅紀錄 恢復播放 返返最後播放到去嗰個位 - 播放清單有排位 + 喺播放清單排第幾 縮圖放到去 1:1 長寬比 顯示喺通知嘅影片縮圖由 16:9 放到去 1:1 長寬比 (話唔定會鬆郁矇) 通知色彩化 @@ -586,4 +586,27 @@ 收起播放清單 唔再收起 係咪要刪除呢個播放清單? + 播放器通知 + 調整目前播放緊嘅串流嘅通知 + 訂閱有新加串流嘅通知 + 載入緊串流詳細資料… + 通知訂閱有新加串流 + 檢查頻率 + 須要網絡連線 + 不拘任何網絡 + 收取通知 + 您現已訂閱呢個頻道 + 全部切換 + 執行檢查有冇新加串流 + 通知 + 新加串流 + + %s 個新加串流 + + 按樂音半度調整音高 + 新加串流通知 + 係咪要喺磁碟機上面消除晒全部下載咗嘅檔案? + 通知已停用 + 單曲 + 節奏步伐 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 0f74f5bde..d93cf4088 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -680,4 +680,25 @@ ExoPlayer 預設值 變更載入間隔大小(目前為 %s)。較低的值可能會提昇初始影片載入速度。變更需要重新啟動播放器。 按音樂半音調整音高 + 播放器通知 + 通知 + 正在載入串流詳細資訊…… + 您已訂閱此頻道 + , + 切換全部 + 新串流通知 + 設定目前播放串流通知 + 需要網路連線 + 從磁碟抹除所有已下載的檔案? + 取得通知 + 新串流 + 關於訂閱的新串流通知 + 執行新串流檢查 + 通知來自訂閱的新串流 + 檢查頻率 + 通知已停用 + + %s 個新串流 + + 任意網路 \ No newline at end of file diff --git a/fastlane/metadata/android/ar_LY/changelogs/63.txt b/fastlane/metadata/android/ar_LY/changelogs/63.txt new file mode 100644 index 000000000..535376e48 --- /dev/null +++ b/fastlane/metadata/android/ar_LY/changelogs/63.txt @@ -0,0 +1,7 @@ +### تحسينات +- استيراد / تصدير إعدادات # 1333 +- تقليل السحب على المكشوف (تحسين الأداء) #1371 +- تحسينات رمز صغير # 1375 +- أضف كل شيء عن الناتج المحلي الإجمالي #1420 +### ثابت +- تنزيل: إصلاح تحطم على تحميل التنزيلات التي لم تنته من .ملفات جيجا # 1407 diff --git a/fastlane/metadata/android/ar_LY/changelogs/64.txt b/fastlane/metadata/android/ar_LY/changelogs/64.txt new file mode 100644 index 000000000..bb2e2f3fd --- /dev/null +++ b/fastlane/metadata/android/ar_LY/changelogs/64.txt @@ -0,0 +1,7 @@ +### تحسينات +- أضيفت القدرة على الحد من جودة الفيديو في حالة استخدام البيانات المتنقلة. #1339 +- تذكر السطوع للدورة # 1442 +- تحسين أداء التنزيل لوحدات المعالجة المركزية الأضعف # 1431 +- إضافة (العمل) دعم الدورة الإعلامية # 1433 +### إصلاح +- فيكس تحطم على فتح التنزيلات (فيكس متاحة الآن ليبني الإصدار) #1441 diff --git a/fastlane/metadata/android/ar_LY/full_description.txt b/fastlane/metadata/android/ar_LY/full_description.txt new file mode 100644 index 000000000..f1a39cbdd --- /dev/null +++ b/fastlane/metadata/android/ar_LY/full_description.txt @@ -0,0 +1 @@ +نيو بايب لا تستخدم أي مكتبات إطار جوجل ، أو يوتيوب API. إنه يوزع الموقع فقط من أجل الحصول على المعلومات التي يحتاجها. لذلك يمكن استخدام هذا التطبيق على الأجهزة دون تثبيت خدمات جوجل. أيضا ، لا تحتاج إلى حساب يوتيوب لاستخدام نيو بايب ، وانها الخيط. diff --git a/fastlane/metadata/android/ar_LY/short_description.txt b/fastlane/metadata/android/ar_LY/short_description.txt new file mode 100644 index 000000000..72c4d0ee6 --- /dev/null +++ b/fastlane/metadata/android/ar_LY/short_description.txt @@ -0,0 +1 @@ +واجهة يوتيوب خفيفة الوزن مجانية للاندرويد diff --git a/fastlane/metadata/android/de/changelogs/985.txt b/fastlane/metadata/android/de/changelogs/985.txt index e2a10be2e..43623578f 100644 --- a/fastlane/metadata/android/de/changelogs/985.txt +++ b/fastlane/metadata/android/de/changelogs/985.txt @@ -1 +1 @@ -Behoben, dass YouTube keinen Stream abspielte. +Behoben, dass YouTube keinen Stream abspielte diff --git a/fastlane/metadata/android/es/changelogs/66.txt b/fastlane/metadata/android/es/changelogs/66.txt new file mode 100644 index 000000000..ff20c3517 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/66.txt @@ -0,0 +1 @@ +- Revisado, minimizar el Reproductor a Imagen Sobre Imagen no destruye el Reproductor cuando no se permite el permiso para la ventana emergente. diff --git a/fastlane/metadata/android/es/changelogs/68.txt b/fastlane/metadata/android/es/changelogs/68.txt new file mode 100644 index 000000000..728f819f9 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/68.txt @@ -0,0 +1,8 @@ +- Mostrar "Recuento de suscriptores no disponible" en esos casos +- Corregir NPE cuando una lista de reproducción de YouTube está vacía +- Corrección rápida de los quioscos en SoundCloud +- Refactorización y corrección de errores #1623 + - Arreglar el resultado cíclico de la búsqueda #1562 + - Corrección de la barra de búsqueda no estática + - Fix YT Premium video no se bloquean correctamente + - Corregir los vídeos que a veces no se cargan (debido al análisis sintáctico de DASH) diff --git a/fastlane/metadata/android/es/changelogs/71.txt b/fastlane/metadata/android/es/changelogs/71.txt new file mode 100644 index 000000000..7c98dafa3 --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/71.txt @@ -0,0 +1,6 @@ +### Mejoras +* Añadir notificación de actualización de la aplicación para la versión de GitHub (#1608 por @krtkush) +* Varias mejoras en el gestor de descargas (#1944 de @kapodamy): +* añadir los iconos blancos que faltan y utilizar la manera de cambiar los colores de los iconos + * comprobar si el iterador está inicializado (corrige #2031) + * permite reintentar las descargas con el error "post-procesamiento fallido" en el nuevo muxer diff --git a/fastlane/metadata/android/es/changelogs/730.txt b/fastlane/metadata/android/es/changelogs/730.txt new file mode 100644 index 000000000..45f336afd --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/730.txt @@ -0,0 +1,2 @@ +# Corregido +-Parche rápido del error de la función de descifrado de nuevo. diff --git a/fastlane/metadata/android/es/changelogs/770.txt b/fastlane/metadata/android/es/changelogs/770.txt index a043c0c9f..f0abd100d 100644 --- a/fastlane/metadata/android/es/changelogs/770.txt +++ b/fastlane/metadata/android/es/changelogs/770.txt @@ -1,4 +1,4 @@ Cambios en 0.17.2 Corregido -- Corregido no hay vídeo estaba disponible +- Corregido, el video no estaba disponible diff --git a/fastlane/metadata/android/fa/changelogs/850.txt b/fastlane/metadata/android/fa/changelogs/850.txt new file mode 100644 index 000000000..ef761776f --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/850.txt @@ -0,0 +1 @@ +در این نسخه سایت یوتیوب آپدیت شد. سایت قدیمی منسوخ شد و دیگر قابل استفاده نخواهد بود، درنتیجه لازم است که شما اپلیکیشن را آپدیت کنید. diff --git a/fastlane/metadata/android/fa/changelogs/982.txt b/fastlane/metadata/android/fa/changelogs/982.txt new file mode 100644 index 000000000..7c5f9147c --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/982.txt @@ -0,0 +1 @@ +مشکل عدم نمایش پخش‌زنده برطرف شد. diff --git a/fastlane/metadata/android/fa/changelogs/985.txt b/fastlane/metadata/android/fa/changelogs/985.txt new file mode 100644 index 000000000..ba5413d49 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/985.txt @@ -0,0 +1 @@ +مشکل عدم نمایش پخش‌زنده برطرف شد diff --git a/fastlane/metadata/android/he/changelogs/986.txt b/fastlane/metadata/android/he/changelogs/986.txt new file mode 100644 index 000000000..53dabb4cf --- /dev/null +++ b/fastlane/metadata/android/he/changelogs/986.txt @@ -0,0 +1,16 @@ +חדש +• התראות על תזרימים חדשים +• מעבר חלק בין נגני רקע ווידאו +• שינוי גובה הצליל בחצאי טונים +• הוספת התור מהנגן הראשי לתור + +שיפורים +• גודל צעד מהירות/גובה צליל נשמר +• צמצום זמן הטעינה הראשונית בנגן הווידאו +• מנשק המשתמש בנגן Android TV השתפר +• אישור לפני מחיקת כל הקבצים שהורדו + +תיקונים +• תוקנה התופעה שכפתורי המדיה לא מסתירים את פקדי הנגן +• תוקנה איפוס נגינה בשינוי סוג נגן +• תוקנה החלפת חלונית רשימת הנגינה diff --git a/fastlane/metadata/android/id/changelogs/986.txt b/fastlane/metadata/android/id/changelogs/986.txt new file mode 100644 index 000000000..886467178 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/986.txt @@ -0,0 +1,16 @@ +Baru +• Notifikasi untuk stream baru +• Transisi tanpa batas antara pemutar latar belakang & video +• Ubah nada dgn semitone +• Tambahkan antrean pemain utama ke daftar putar + +Perbaikan +• Ingat ukuran langkah kecepatan/nada +• Kurangi buffering panjang awal di pemutar video +• Tingkatkan UI pemutar utk Android TV +• Konfirmasi sblm menghapus semua file yang diunduh + +Diperbaiki +• Tombol media agar tdk menyembunyikan kontrol pemain +• Reset pemutaran pada perubahan tipe pemutar +• Memutar dialog dftr putar diff --git a/fastlane/metadata/android/nb-NO/changelogs/986.txt b/fastlane/metadata/android/nb-NO/changelogs/986.txt new file mode 100644 index 000000000..d60a5e1d5 --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/986.txt @@ -0,0 +1,16 @@ +Nytt +• Merknader for nye strømmer +• Sømløs overgang i bakgrunn og videospillere +• Endre toneart med halvtoner +• Legg til hovedspillerkøen på slutten av en spilleliste + +Forbedret +• Husk hastighet/toneart-stegstørrelse +• Unngå lang mellomlagring i videospilleren +• Forbedret spillergrensesnitt for Android TV +• Bekreft sletting av alle nedlastede filer + +Fikset +• Mediaknapper som ikke skjuler avspillerkontroller +• Avspillingstilbakestilling ved endring av spillertype +• Rotering av spillelistedialog diff --git a/fastlane/metadata/android/pl/changelogs/986.txt b/fastlane/metadata/android/pl/changelogs/986.txt new file mode 100644 index 000000000..c9146e57c --- /dev/null +++ b/fastlane/metadata/android/pl/changelogs/986.txt @@ -0,0 +1,16 @@ +Nowe +• Powiadom. o nowych strumieniach +• Płynne przejście między odtwarzacz. w tle i wideo +• Zmiana wys. tonu o półtony +• Dołącz. głównej kolejki odtwarzacza do playlisty + +Ulepszone +• Zapamięt. wielkości kroku prędk./wys. dźwięku +• Zmniej. wstępne długie bufor. w odtwarzaczu +• Ulepsz. odtwarzacza dla Android TV +• Potwierdz. przed usun. wszystkich plików + +Naprawione +• Przycisk multimediów nie ukrywał kontrolek odtwarzacza +• Reset odtwarzania po zmianie typu odtwarzacz. +• Obracanie okna playlisty diff --git a/fastlane/metadata/android/ru/changelogs/982.txt b/fastlane/metadata/android/ru/changelogs/982.txt new file mode 100644 index 000000000..0ea2b74e6 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/982.txt @@ -0,0 +1 @@ +Исправлена ошибка, из-за которой YouTube не воспроизводил ни одну трансляцию. diff --git a/fastlane/metadata/android/sk/changelogs/64.txt b/fastlane/metadata/android/sk/changelogs/64.txt new file mode 100644 index 000000000..aa9fd1f03 --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/64.txt @@ -0,0 +1,8 @@ +### Vylepšenia +- Pridaná možnosť obmedziť kvalitu videa pri používaní mobilných dát. #1339 +- Zapamätanie jasu pre reláciu #1442 +- Zlepšenie výkonu sťahovania pre slabšie procesory #1431 +- Pridanie (funkčnej) podpory pre reláciu médií #1433 + +### Opravy +- Oprava zlyhania pri otváraní sťahovania (oprava je teraz k dispozícii pre verzie vydania) #1441 diff --git a/fastlane/metadata/android/sv/changelogs/969.txt b/fastlane/metadata/android/sv/changelogs/969.txt new file mode 100644 index 000000000..a9ecf6b67 --- /dev/null +++ b/fastlane/metadata/android/sv/changelogs/969.txt @@ -0,0 +1,13 @@ +• Tillåt installation på extern lagring + +• [Bandcamp] Stöd för att visa de tre första kommentarerna i en stream har lagts till. + +• Visa endast "nedladdning har börjat" när nedladdningen har påbörjats. + +• Ställ inte in reCaptcha-cookie när det inte finns någon cookie lagrad. + +• Player] Förbättra prestanda för cache + +• Avskaffa tidigare Snackbars när nedladdningar raderas + +• Fixat att försöka radera objekt som inte finns i listan diff --git a/fastlane/metadata/android/tr/changelogs/910.txt b/fastlane/metadata/android/tr/changelogs/910.txt new file mode 100644 index 000000000..c3bc3a516 --- /dev/null +++ b/fastlane/metadata/android/tr/changelogs/910.txt @@ -0,0 +1 @@ +Bazı nadir durumlarda NewPipe'ın başlamasını engelleyen veri tabanı taşıması düzeltildi. diff --git a/fastlane/metadata/android/tr/changelogs/963.txt b/fastlane/metadata/android/tr/changelogs/963.txt new file mode 100644 index 000000000..f56a60157 --- /dev/null +++ b/fastlane/metadata/android/tr/changelogs/963.txt @@ -0,0 +1 @@ +• [YouTube] Kanal devam ettirme düzeltildi diff --git a/fastlane/metadata/android/tr/changelogs/982.txt b/fastlane/metadata/android/tr/changelogs/982.txt new file mode 100644 index 000000000..9e2b10e2d --- /dev/null +++ b/fastlane/metadata/android/tr/changelogs/982.txt @@ -0,0 +1 @@ +YouTube'un herhangi bir akışı oynatmaması düzeltildi. diff --git a/fastlane/metadata/android/tr/changelogs/985.txt b/fastlane/metadata/android/tr/changelogs/985.txt new file mode 100644 index 000000000..e5979c68d --- /dev/null +++ b/fastlane/metadata/android/tr/changelogs/985.txt @@ -0,0 +1 @@ +YouTube'un herhangi bir akışı oynatmaması düzeltildi diff --git a/fastlane/metadata/android/uk/changelogs/985.txt b/fastlane/metadata/android/uk/changelogs/985.txt index 41d4d11ce..905287c74 100644 --- a/fastlane/metadata/android/uk/changelogs/985.txt +++ b/fastlane/metadata/android/uk/changelogs/985.txt @@ -1 +1 @@ -Виправлено проблему невідтворюваності трансляцій YouTube. +Виправлено проблему невідтворюваності трансляцій diff --git a/fastlane/metadata/android/uk/changelogs/986.txt b/fastlane/metadata/android/uk/changelogs/986.txt new file mode 100644 index 000000000..f4e9472b1 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/986.txt @@ -0,0 +1,13 @@ +Нове +• Сповіщення про нові потоки +• Плавний перехід між фоновими та відеопрогравачем +• Додавання основної черги програвача до добірки + +Поліпшення +• Згладження початкової тривалої буферизації у відеопрогравачі +• Покращення інтерфейсу користувача програвача для Android TV +• Підтвердження перед видаленням усіх завантажених файлів + +Виправлення +• Виправлення кнопки мультимедіа, яка не приховує елементи керування програвачем +• Виправлено обертання діалогового вікна списку відтворення diff --git a/fastlane/metadata/android/zh-Hans/changelogs/986.txt b/fastlane/metadata/android/zh-Hans/changelogs/986.txt new file mode 100644 index 000000000..9a23508cb --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/986.txt @@ -0,0 +1,16 @@ +新增 +• 新串流的通知 +• 后台播放和(前台)d播放器之间的无缝过渡 +• 通过半音改变音高 +• 将主播放器队列追加至一播放列表 + +改进 +• 记住速度/音调步长 +• 减少视频播放器中初始时间缓冲 +• 改进 Android TV 的播放器界面 +• 在删除所有下载的文件之前进去确认 + +修复 +• 使用物理媒体按钮(如耳机按键等)不会隐藏播放器控件 +• 播放器类型更改时重置播放 +• 旋转播放列表对话框的异常 diff --git a/fastlane/metadata/android/zh-Hant/changelogs/985.txt b/fastlane/metadata/android/zh-Hant/changelogs/985.txt new file mode 100644 index 000000000..4e8bf6537 --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/985.txt @@ -0,0 +1 @@ +修正 YouTube 無法播放任何串流 diff --git a/fastlane/metadata/android/zh-Hant/changelogs/986.txt b/fastlane/metadata/android/zh-Hant/changelogs/986.txt new file mode 100644 index 000000000..964a2d74f --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/986.txt @@ -0,0 +1,16 @@ +新增 +• 新串流通知 +• 背景與影片播放器之間無縫轉換 +• 按半音調整音高 +• 將主播放器佇列追加至播放清單中 + +改進 +• 記住速度/音高步距 +• 避免影片播放器初始緩衝太久 +• 改善 Android TV 的播放器介面 +• 刪除全部已下載檔案前先確認 + +修正 +• 修正媒體鍵未有隱藏播放器控制項 +• 修正播放器類型變更時重新播放 +• 修正播放清單對話方塊的縱橫旋轉 diff --git a/fastlane/metadata/android/zh_Hant_HK/changelogs/985.txt b/fastlane/metadata/android/zh_Hant_HK/changelogs/985.txt new file mode 100644 index 000000000..f1d67925f --- /dev/null +++ b/fastlane/metadata/android/zh_Hant_HK/changelogs/985.txt @@ -0,0 +1 @@ +修正 YouTube 乜串流都播唔到 diff --git a/fastlane/metadata/android/zh_Hant_HK/changelogs/986.txt b/fastlane/metadata/android/zh_Hant_HK/changelogs/986.txt new file mode 100644 index 000000000..e451189ef --- /dev/null +++ b/fastlane/metadata/android/zh_Hant_HK/changelogs/986.txt @@ -0,0 +1,16 @@ +新嘢 +• 新加串流有通知 +• 幕後同影片播放器無縫切換 +• 按半度調整音高 +• 將主版面播放器排隊播追加落播放清單 + +進步 +• 記住速率/音高調整步伐 +• 改善影片播放器開波時漫長緩衝 +• 完善 Android TV 嘅播放器版面 +• 刪除晒全部下載咗嘅檔案之前先確認 + +修正 +• 修正媒體掣冇隱藏到啲播放器控掣 +• 修正播放器類型有變時重頭再播 +• 修正播放器清單對話方塊嘅方向旋轉 diff --git a/fastlane/metadata/android/zh_Hant_HK/full_description.txt b/fastlane/metadata/android/zh_Hant_HK/full_description.txt index 1a516a17d..9fe7dfeb9 100644 --- a/fastlane/metadata/android/zh_Hant_HK/full_description.txt +++ b/fastlane/metadata/android/zh_Hant_HK/full_description.txt @@ -1 +1 @@ -NewPipe 沒有使用任何 Google 框架庫組件或是 YouTube API。NewPipe 只需解析網頁以獲取所需資訊,因此 NewPipe 可在沒有安裝 Google 服務的裝置上使用。此外,你亦無需 YouTube 帳號就能使用NewPipe,NewPipe 是自由及開放源碼軟件(FLOSS)。 +NewPipe 冇用到任何 Google 框架程式庫又或者 YouTube API,單憑解析網站去攞到所需嘅資訊,因此就算冇裝 Google 服務嘅機都用得呢個 app。此外,NewPipe 亦毋需您要有 YouTube 帳戶就用得,而且係自由同開放原始碼嘅軟件 (FLOSS)。 diff --git a/fastlane/metadata/android/zh_Hant_HK/short_description.txt b/fastlane/metadata/android/zh_Hant_HK/short_description.txt index fca9de0da..8f4a31c03 100644 --- a/fastlane/metadata/android/zh_Hant_HK/short_description.txt +++ b/fastlane/metadata/android/zh_Hant_HK/short_description.txt @@ -1 +1 @@ -一個自由輕量的 Android YouTube 前端。 +一個自由輕巧嘅 Android YouTube 前端。 From b8dbb3f0733288fac580b9f09ae8399a7bbd24a2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 28 Apr 2022 11:32:33 +0200 Subject: [PATCH 084/992] Use 64 KiB as the default progressive load interval This ensures a small value is used by default, solving buffering issues at the beginning of videos --- .../org/schabi/newpipe/player/helper/PlayerHelper.java | 2 +- app/src/main/res/values/settings_keys.xml | 9 +++++---- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 513e94419..b73c6cf7f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -426,7 +426,7 @@ public final class PlayerHelper { context.getString(R.string.progressive_load_interval_key), context.getString(R.string.progressive_load_interval_default_value)); - if (context.getString(R.string.progressive_load_interval_default_value) + if (context.getString(R.string.progressive_load_interval_exoplayer_default_value) .equals(preferredIntervalBytes)) { return ProgressiveMediaSource.DEFAULT_LOADING_CHECK_INTERVAL_BYTES; } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index d72be0ccf..bf42aaf0e 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -56,20 +56,21 @@ progressive_load_interval - default + 64 + exoplayer_default 1 KiB 16 KiB 64 KiB 256 KiB - @string/progressive_load_interval_default + @string/progressive_load_interval_exoplayer_default 1 16 - 64 + @string/progressive_load_interval_default_value 256 - default + @string/progressive_load_interval_exoplayer_default_value minimize_on_exit_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 792e6414b..0af08a8ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -733,7 +733,7 @@ On Off - ExoPlayer default + ExoPlayer default Notifications are disabled Get notified From 6a4d8329c35e6c67c8192d47a6d05fbfbc7f8b66 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 29 Apr 2022 16:11:28 +0200 Subject: [PATCH 085/992] Rename progressive_load_interval_exoplayer_default for all languages --- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-b+zh+HANS+CN/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-et/strings.xml | 2 +- app/src/main/res/values-eu/strings.xml | 2 +- app/src/main/res/values-fa/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-gl/strings.xml | 2 +- app/src/main/res/values-he/strings.xml | 2 +- app/src/main/res/values-in/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-nb-rNO/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt-rPT/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sc/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 2 +- app/src/main/res/values-zh-rHK/strings.xml | 2 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- 30 files changed, 30 insertions(+), 30 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 7022a2c29..3bb304848 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -735,7 +735,7 @@ LeakCanary غير متوفر ضبط الصوت من خلال النغمات الموسيقية النصفية خطوة الإيقاع - الافتراضي ExoPlayer + الافتراضي ExoPlayer تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل. تكوين إشعار مشغل البث الحالي الإشعارات diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index 26c930886..c5b9e9646 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -676,7 +676,7 @@ 以音乐半音调整音高 节奏步长 改变加载间隔的大小(当前%s),较低的值可以加快初始的视频加载速度,改变需要重启播放器。 - ExoPlayer 默认 + ExoPlayer 默认 配置当前正在播放的串流的通知 新串流通知 检查频率 diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 98523442a..84c957f1c 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -700,5 +700,5 @@ LeakCanary není dostupné Upravit výšku tónů po půltónech Krok tempa - Výchozí ExoPlayer + Výchozí ExoPlayer \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 51224727d..1b47fba2f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -690,7 +690,7 @@ Tonhöhe nach musikalischen Halbtönen anpassen Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players. Geschwindigkeitsstufe - ExoPlayer Standard + ExoPlayer Standard Benachrichtigungen Benachrichtigen über neue abonnierbare Streams Neue Streams diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 769292f58..b54b34d60 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -687,7 +687,7 @@ Το LeakCanary δεν είναι διαθέσιμο Προσαρμόστε τον τόνο με βάση τα μουσικά ημιτόνια Βήμα τέμπο - Εξ\' ορισμού ExoPlayer + Εξ\' ορισμού ExoPlayer Αλλάξτε το μέγεθος του διαστήματος φόρτωσης (επί του παρόντος είναι %s). Μια χαμηλότερη τιμή μπορεί να επιταχύνει την αρχική φόρτωση βίντεο. Οι αλλαγές απαιτούν επανεκκίνηση της εφαρμογής. Ειδοποιήσεις diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2bef8db08..f386801e2 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -688,7 +688,7 @@ Comentario fijado Ya se reproduce en segundo plano LeakCanary no está disponible - ExoPlayer valor por defecto + ExoPlayer valor por defecto Paso de tempo Cambia el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial del vídeo. Los cambios requieren un reinicio del reproductor. Ajustar el tono por semitonos musicales diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 055f103c6..d04834f8a 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -685,7 +685,7 @@ \nPalun paigalda nutiseadmesse failihaldur, mis järgib Storage Access Framework reeglistikku.
Esiletõstetud kommentaar LeakCanary pole saadaval - ExoPlayer\'i vaikimisi väärtused + ExoPlayer\'i vaikimisi väärtused Muuda video laadimise välpa (hetkel %s). Väiksemast väärtusest võib abi olla, kui tahad et video esitamine algaks varem. Muudatuste jõustamine eeldab rakenduse uuesti käivitamist. Meediamängija teavitused Teavitused pole kasutusel diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 7faed5800..1c710641e 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -709,6 +709,6 @@ LeakCanary ez dago eskuragarri Egiaztapen maiztasuna Jakinarazi - ExoPlayer lehenetsia + ExoPlayer lehenetsia Beharrezko sare konexioa \ No newline at end of file diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 3186deb72..d0f4d3d84 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -688,7 +688,7 @@ گام سرعت تنظیم زیر و بم با شبه‌تن‌ها تغییر اندازهٔ بازهٔ بار (هم‌اکنون %s). مقداری پایین‌تر، می‌تواند بار کردن نخستین ویدیو را سرعت بخشد. تغییرها نیاز به یک آغاز دوبارهٔ پخش‌کننده دارند. - پیش‌گزیدهٔ اگزوپلیر + پیش‌گزیدهٔ اگزوپلیر آگاهی‌ها بار کردن جزییات جریان… اجرای بررسی برای جریان‌های جدید diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9b103256d..4a4559477 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -690,7 +690,7 @@ Modifie la taille de l\'intervalle de chargement (actuellement %s). Une valeur plus faible peut accélérer le chargement initial des vidéos . Règler la hauteur par demi-tons musicaux Pas du tempo - Valeur par défaut d’ExoPlayer + Valeur par défaut d’ExoPlayer Nouveaux flux Configurer la notification du flux en cours de lecture Recevoir des notifications diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index f8b0b3a06..52225b843 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -682,7 +682,7 @@ Amosar fitas coloridas de Picasso na cima das imaxes que indican a súa fonte: vermello para a rede, azul para o disco e verde para a memoria O \'Sistema de Acceso ao almacenamento\' non está soportado en Android KitKat e anteriores Novos elementos - Predefinido do ExoPlayer + Predefinido do ExoPlayer Amosar \"Travar o reprodutor\" Amosar un snackbar de erro Amosa unha opción de travamento ao usar o reprodutor diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index f6dd702b4..a0e6c32a9 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -711,7 +711,7 @@ LeakCanary אינה זמינה התאמת גובה הצליל לפי חצאי טונים מוזיקליים צעד מקצב - ברירת מחדל של ExoPlayer + ברירת מחדל של ExoPlayer שינוי גודל מרווח הטעינה (כרגע %s). ערך נמוך יותר עשוי להאיץ את טעינת הווידאו הראשונית. שינויים דורשים את הפעלת הנגן מחדש. התראות על תזרימים חדשים להרשמה תדירות בדיקה diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 83a4a147c..056cdfce4 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -674,7 +674,7 @@ Sudah diputar di latar belakang LeakCanary tidak tersedia Langkah tempo - Default ExoPlayer + Default ExoPlayer Atur nada berdasarkan semitone musik Ubah ukuran interval pemuatan (saat ini %s). Sebuah nilai yang rendah mungkin dapat membuat pemuatan video awal lebih cepat. Membutuhkan sebuah pemulaian ulang pada pemain. Memuat detail stream… diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 6e1b213e4..9f7b74d8e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -686,7 +686,7 @@ Già in riproduzione in sottofondo LeakCanary non è disponibile Regola il tono secondo i semitoni musicali - Predefinito ExoPlayer + Predefinito ExoPlayer Cambia la dimensione dell\'intervallo da caricare (attualmente %s). Un valore basso può velocizzare il caricamento iniziale del video. La modifica richiede il riavvio del lettore. Passo tempo Notifiche di nuove stream dalle iscrizioni diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 9469d81e0..a0699f229 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -679,7 +679,7 @@ 登録チャンネルの新しいストリームについて通知する , 通知は無効化されています - ExoPlayer のデフォルト + ExoPlayer のデフォルト 通知を受け取る このチャンネルを購読しました ディスクからダウンロードしたすべてのファイルを削除しますか? diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index fe48ef520..92b24fbc8 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -681,7 +681,7 @@ Installer en filbehandler først, eller skru av «%s» i nedlastingsinnstillingene. Installer en filbehandler som støtter lagringstilgangsrammeverk først. LeakCanary er ikke tilgjengelig - ExoPlayer-forvalg + ExoPlayer-forvalg Juster toneart etter musikalske halvtoner Tempo-steg \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index efbb64f6a..dd405963a 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -688,5 +688,5 @@ Verander de laad interval tijd (nu %s). Een lagere waarde kan het initiële laden van de video versnellen. De wijziging vereist een herstart van de speler. Pas de toonhoogte aan met muzikale halve tonen Tempo stap - ExoPlayer standaard + ExoPlayer standaard \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a13dec423..cfa995212 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -706,7 +706,7 @@ LeakCanary jest niedostępne Rozmiar interwału ładowania odtwarzania Zmień rozmiar interwału ładowania (aktualnie %s). Niższa wartość może przyspieszyć początkowe ładowanie wideo. Zmiany wymagają ponownego uruchomienia odtwarzacza - domyślny ExoPlayera + domyślny ExoPlayera Dostosuj wysokość półtonami Krok tempa Powiadomienie odtwarzacza diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 1746d6615..0850034f6 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -688,7 +688,7 @@ Passo do tempo Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. As alterações exigem que o player reinicie. Ajustar o tom por semitons musicais - ExoPlayer padrão + ExoPlayer padrão Notificação do reprodutor Configurar a notificação do fluxo da reprodução atual Notificações diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 8e2cd1366..43e782251 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -688,7 +688,7 @@ Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar. Ajustar o tom por semitons musicais Passo do tempo - Predefinido do ExoPlayer + Predefinido do ExoPlayer Notificações A carregar detalhes do fluxo… Verificar se há novos fluxos diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d6993c012..9e1a80916 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -687,7 +687,7 @@ Comentário fixado LeakCanary não está disponível Ajustar o tom por semitons musicais - Predefinido do ExoPlayer + Predefinido do ExoPlayer Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar. Passo do tempo Notificação do reprodutor diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6527c8d36..fc4c03f42 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -723,7 +723,7 @@ LeakCanary недоступна Регулировка высоты тона по музыкальным полутонам Шаг темпа - Стандартное значение ExoPlayer + Стандартное значение ExoPlayer Изменить размер интервала загрузки (сейчас %s). Меньшее значение может ускорить начальную загрузку видео. Изменение значения потребует перезапуска плеера. Загрузка деталей трансляции… Проверить на наличие новых трансляций diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 1a6456865..38a7c3303 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -687,7 +687,7 @@ LeakCanary no est a disponimentu Règula s\'intonatzione in base a sos semitonos musicales Passu de tempus - Valore ExoPlayer predefinidu + Valore ExoPlayer predefinidu Muda sa mannària de s\'intervallu de carrigamentu (in custu momentu %s). Unu valore prus bassu diat pòdere allestrare su carrigamentu de incumintzu de su vìdeu. Sas modìficas tenent bisòngiu de torrare a allùghere su riproduidore. Cunfigura sa notìfica de su flussu in cursu de riprodutzione Notìficas de flussos noos dae sas iscritziones diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index c630eae16..9b7a62145 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -700,7 +700,7 @@ Oznámte chybu Upraviť výšku poltónov Krok tempa - ExoPlayer preddefinovaný + ExoPlayer preddefinovaný Zmeniť interval načítania (aktuálne %s). Menšia hodnota môže zvýšiť rýchlosť prvotného načítania videa. Zmena vyžaduje reštart. Upozornenia Frekvencia kontroly diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 95d5ea82f..63af6d4d6 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -686,7 +686,7 @@ Fäst kommentar LeakCanary är inte tillgänglig Justera tonhöjden med musikaliska halvtoner - ExoPlayer standard + ExoPlayer standard Ändra inläsningsintervallets storlek (för närvarande %s). Ett lägre värde kan påskynda den första videoinläsningen. Ändringar kräver omstart av spelaren. Temposteg Validera frekvens diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 4dd10063b..958dc5eeb 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -686,7 +686,7 @@ Sabitlenmiş yorum LeakCanary yok Yükleme ara boyutunu değiştir (şu anda %s). Düşük bir değer videonun ilk yüklenişini hızlandırabilir. Değişiklikler oynatıcının yeniden başlatılmasını gerektirir. - ExoPlayer öntanımlısı + ExoPlayer öntanımlısı Tempo adımı Perdeyi müzikal yarım tonlarla uyarla Yeni akış bildirimleri diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 06e51f576..f2a3e986e 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -704,7 +704,7 @@ LeakCanary недоступний Крок темпу Регулювання висоти звуку за музичними півтонами - Типовий ExoPlayer + Типовий ExoPlayer Змінити розмір інтервалу завантаження (наразі %s). Менше значення може прискорити початкове завантаження відео. Зміни вимагають перезапуску програвача. Ви підписалися на цей канал , diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 71838b160..7ea1a1ed5 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -674,6 +674,6 @@ LeakCanary không khả dụng Điều chỉnh cao độ theo nhạc nền âm nhạc Nhịp độ tiếp theo - ExoPlayer mặc định + ExoPlayer mặc định Bình luận được ghim \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 2a00a818e..0e79f5a58 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -577,7 +577,7 @@ \n您肯定?剷走咗冇得掹個囉喎!
「儲存空間存取框架」呢壇嘢,俾您下載落外置 SD 卡嗰度 Android 10 打上局住要用「儲存空間存取框架」 - ExoPlayer 預設值 + ExoPlayer 預設值 外置儲存空間用唔到 排隊尾輪候播 刪除咗搜尋紀錄 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index d93cf4088..fd129fb6c 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -677,7 +677,7 @@ 已經在背景播放 LeakCanary 無法使用 步進時間 - ExoPlayer 預設值 + ExoPlayer 預設值 變更載入間隔大小(目前為 %s)。較低的值可能會提昇初始影片載入速度。變更需要重新啟動播放器。 按音樂半音調整音高 播放器通知 From 047fe21c143fd945de34aa01c5efa4df033cb977 Mon Sep 17 00:00:00 2001 From: kt programs Date: Sat, 30 Apr 2022 17:43:30 +0800 Subject: [PATCH 086/992] Fix hiding player controls when playing from media button DefaultControlDispatcher was removed in ExoPlayer 2.16.0, so the class extending it that handled play/pause was removed in #8020. The new solution is to use an instance of ForwardingPlayer. Call sessionConnector.setPlayer with an instance of ForwardingPlayer that overrides play() and pause() and calls the callback methods. --- .../newpipe/player/helper/MediaSessionManager.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java index c12ba754a..a8735dc08 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java @@ -13,6 +13,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.media.session.MediaButtonReceiver; +import com.google.android.exoplayer2.ForwardingPlayer; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; @@ -55,7 +56,17 @@ public class MediaSessionManager { sessionConnector = new MediaSessionConnector(mediaSession); sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback)); - sessionConnector.setPlayer(player); + sessionConnector.setPlayer(new ForwardingPlayer(player) { + @Override + public void play() { + callback.play(); + } + + @Override + public void pause() { + callback.pause(); + } + }); } @Nullable From 173b6c3f00df02e64151ee642323205400d412f2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 30 Apr 2022 21:46:06 +0200 Subject: [PATCH 087/992] Fix wrong NonNull --- .../schabi/newpipe/player/helper/PlaybackParameterDialog.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 2d1461aaf..7220335d1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -38,8 +38,6 @@ import java.util.function.DoubleConsumer; import java.util.function.DoubleFunction; import java.util.function.DoubleSupplier; -import javax.annotation.Nonnull; - import icepick.Icepick; import icepick.State; @@ -498,7 +496,7 @@ public class PlaybackParameterDialog extends DialogFragment { ) { return new SimpleOnSeekBarChangeListener() { @Override - public void onProgressChanged(@Nonnull final SeekBar seekBar, + public void onProgressChanged(@NonNull final SeekBar seekBar, final int progress, final boolean fromUser) { if (fromUser) { // ensure that the user triggered the change From 7e50eed95e25928cc4a40d9308ff38c216a3de0e Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 1 May 2022 20:50:37 +0200 Subject: [PATCH 088/992] Removed unused string resources --- app/src/main/res/values-ar/strings.xml | 3 --- app/src/main/res/values-b+zh+HANS+CN/strings.xml | 3 --- app/src/main/res/values-ca/strings.xml | 1 - app/src/main/res/values-ckb/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 3 --- app/src/main/res/values-de/strings.xml | 3 --- app/src/main/res/values-el/strings.xml | 3 --- app/src/main/res/values-es/strings.xml | 3 --- app/src/main/res/values-et/strings.xml | 3 --- app/src/main/res/values-eu/strings.xml | 3 --- app/src/main/res/values-fa/strings.xml | 3 --- app/src/main/res/values-fr/strings.xml | 3 --- app/src/main/res/values-gl/strings.xml | 2 -- app/src/main/res/values-he/strings.xml | 3 --- app/src/main/res/values-hu/strings.xml | 1 - app/src/main/res/values-ia/strings.xml | 1 - app/src/main/res/values-in/strings.xml | 3 --- app/src/main/res/values-it/strings.xml | 3 --- app/src/main/res/values-ja/strings.xml | 2 -- app/src/main/res/values-nb-rNO/strings.xml | 3 --- app/src/main/res/values-nl/strings.xml | 3 --- app/src/main/res/values-pl/strings.xml | 3 --- app/src/main/res/values-pt-rBR/strings.xml | 3 --- app/src/main/res/values-pt-rPT/strings.xml | 3 --- app/src/main/res/values-pt/strings.xml | 4 ---- app/src/main/res/values-ro/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 5 ----- app/src/main/res/values-sc/strings.xml | 3 --- app/src/main/res/values-sk/strings.xml | 4 ---- app/src/main/res/values-sv/strings.xml | 3 --- app/src/main/res/values-ta/strings.xml | 1 - app/src/main/res/values-te/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 3 --- app/src/main/res/values-uk/strings.xml | 4 ---- app/src/main/res/values-vi/strings.xml | 2 -- app/src/main/res/values-zh-rHK/strings.xml | 2 -- app/src/main/res/values-zh-rTW/strings.xml | 6 ------ 37 files changed, 101 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 3bb304848..e7da4611a 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -730,11 +730,8 @@ إظهار خطأ snackbar لم يتم العثور على مدير ملفات مناسب لهذا الإجراء. \nالرجاء تثبيت مدير ملفات متوافق مع Storage Access Framework. - يتم تشغيله في الخلفية تعليق مثبت LeakCanary غير متوفر - ضبط الصوت من خلال النغمات الموسيقية النصفية - خطوة الإيقاع الافتراضي ExoPlayer تغيير حجم الفاصل الزمني للتحميل (حاليا %s). قد تؤدي القيمة الأقل إلى تسريع تحميل الفيديو الأولي. تتطلب التغييرات إعادة تشغيل المشغل. تكوين إشعار مشغل البث الحالي diff --git a/app/src/main/res/values-b+zh+HANS+CN/strings.xml b/app/src/main/res/values-b+zh+HANS+CN/strings.xml index c5b9e9646..917016675 100644 --- a/app/src/main/res/values-b+zh+HANS+CN/strings.xml +++ b/app/src/main/res/values-b+zh+HANS+CN/strings.xml @@ -670,11 +670,8 @@ 找不到适合此操作的文件管理器。 \n请安装与存储访问框架(SAF)兼容的文件管理器。 NewPipe 遇到了一个错误,点击此处报告此错误 - 已经在后台播放 置顶评论 LeakCanary 不可用 - 以音乐半音调整音高 - 节奏步长 改变加载间隔的大小(当前%s),较低的值可以加快初始的视频加载速度,改变需要重启播放器。 ExoPlayer 默认 配置当前正在播放的串流的通知 diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 421139875..a42e68940 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -650,7 +650,6 @@ Inicia el reproductor principal en pantalla completa Llisqueu els elements per eliminar-los Si la rotació automàtica està bloquejada, no inicieu vídeos al mini reproductor, sinó que aneu directament al mode de pantalla completa. Podeu accedir igualment al mini reproductor sortint de pantalla completa - Ja s\'està reproduint en segon pla Notificació d\'informe d\'error Tancar abruptament el reproductor Comprovar si hi ha actualitzacions diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index f621b88bd..399d2360e 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -672,7 +672,6 @@ پیشاندانی ”کڕاش کردنی لێدەرەکە“ سازاندنی پەیامی کێشەیەک پشکنین بۆ نوێکردنەوە - وا لە پاشبنەمادا لێدەدرێت کێشە لە سکاڵا کردنی پەیام پەیامەکانی سکاڵاکردن لە کێشەکان بابەتە نوێیەکانی فیید diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 84c957f1c..9ba0d1e98 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -684,7 +684,6 @@ Vytvořit oznámení o chybě Kontrola aktualizací… Ukázat „Shodit přehrávač“ - Hraje již v pozadí Nové položky feedů Pro tuto akci nebyl nalezen žádný vhodný správce souborů. \nProsím, nainstalujte správce souborů kompatibilní se Storage Access Framework. @@ -698,7 +697,5 @@ Shodit přehrávač Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače. LeakCanary není dostupné - Upravit výšku tónů po půltónech - Krok tempa Výchozí ExoPlayer \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1b47fba2f..ffc501c63 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -684,12 +684,9 @@ \nBitte installiere einen Dateimanager oder versuche, \'%s\' in den Downloadeinstellungen zu deaktivieren.
Es wurde kein geeigneter Dateimanager für diese Aktion gefunden. \nBitte installiere einen Storage Access Framework kompatiblen Dateimanager. - Wird bereits im Hintergrund abgespielt Angehefteter Kommentar LeakCanary ist nicht verfügbar - Tonhöhe nach musikalischen Halbtönen anpassen Ändern der Größe des Ladeintervalls (derzeit %s). Ein niedrigerer Wert kann das anfängliche Laden des Videos beschleunigen. Änderungen erfordern einen Neustart des Players. - Geschwindigkeitsstufe ExoPlayer Standard Benachrichtigungen Benachrichtigen über neue abonnierbare Streams diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index b54b34d60..e202d2b34 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -682,11 +682,8 @@ \nΕγκαταστήστε έναν συμβατό με το Πλαίσιο Πρόσβασης Αποθήκευσης.
Το NewPipe παρουσίασε ένα σφάλμα. Πατήστε για αναφορά Εμφάνιση μιας snackbar σφάλματος - Αναπαράγεται ήδη στο παρασκήνιο Καρφιτσωμένο σχόλιο Το LeakCanary δεν είναι διαθέσιμο - Προσαρμόστε τον τόνο με βάση τα μουσικά ημιτόνια - Βήμα τέμπο Εξ\' ορισμού ExoPlayer Αλλάξτε το μέγεθος του διαστήματος φόρτωσης (επί του παρόντος είναι %s). Μια χαμηλότερη τιμή μπορεί να επιταχύνει την αρχική φόρτωση βίντεο. Οι αλλαγές απαιτούν επανεκκίνηση της εφαρμογής. Ειδοποιήσεις diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index f386801e2..bb0decc9a 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -686,12 +686,9 @@ No se encontró ningún gestor de archivos adecuado para esta acción. \nPor favor instale un gestor de archivos compatible con \"Sistema de Acceso al Almacenamiento\". Comentario fijado - Ya se reproduce en segundo plano LeakCanary no está disponible ExoPlayer valor por defecto - Paso de tempo Cambia el tamaño del intervalo de carga (actualmente %s). Un valor más bajo puede acelerar la carga inicial del vídeo. Los cambios requieren un reinicio del reproductor. - Ajustar el tono por semitonos musicales Notificaciones Nuevos streams Notificación del reproductor diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index d04834f8a..029ff9878 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -674,7 +674,6 @@ NewPipe töös tekkis viga, sellest teavitamiseks klõpsi Jooksuta meediamängija kokku Näita veateate akent - Meedia esitamine taustal toimib juba Teavitus vigadest Teavitused vigadest informeerimiseks Tekkis viga, vaata vastavat teadet @@ -709,6 +708,4 @@ Sa oled nüüd selle kanali tellija , Lülita kõik sisse - Reguleeri helikõrgust muusikaliste pooltoonide kaupa - Tempo samm \ No newline at end of file diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 1c710641e..d4bddf4a8 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -666,7 +666,6 @@ Gehitu bideo hau isatsari Erakutsi \"Itxi erreproduzigailua\" Prozesatzen... Itxoin mesedez - Atzeko planoan erreproduzitzen dagoeneko Erroreen txostenen jakinarazpena Jakinarazpenak erroreen berri emateko NewPipe-k errore bat aurkitu du, sakatu berri emateko @@ -695,8 +694,6 @@ Kanal honetara harpidetu zara , Txandakatu denak - Doitu tonua semitono musikalen arabera - Tempo urratsa Aldatu karga maiztasun tamaina (unean %s). Balio txikiago batek bideoaren hasierako karga azkartu dezake. Erreproduzigailuaren berrabiarazte bat behar du. Harpidetzen jario berriei buruz jakinarazi Ezabatu deskargatutako fitxategi guztiak biltegitik\? diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index d0f4d3d84..dac49883d 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -683,10 +683,7 @@ نیوپایپ به خطایی برخورد. برای گزارش، بزنید خطایی رخ داد. آگاهی را ببینید نظر سنجاق شده - در حال پخش در پس‌زمینه لیک‌کاناری موجود نیست - گام سرعت - تنظیم زیر و بم با شبه‌تن‌ها تغییر اندازهٔ بازهٔ بار (هم‌اکنون %s). مقداری پایین‌تر، می‌تواند بار کردن نخستین ویدیو را سرعت بخشد. تغییرها نیاز به یک آغاز دوبارهٔ پخش‌کننده دارند. پیش‌گزیدهٔ اگزوپلیر آگاهی‌ها diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4a4559477..1716cc06b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -685,11 +685,8 @@ Aucun gestionnaire de fichier approprié n\'a été trouvé pour cette action. \nVeuillez installer un gestionnaire de fichiers ou essayez de désactiver \'%s\' dans les paramètres de téléchargement. Commentaire épinglé - Une lecture est déjà en arrière-plan LeakCanary n\'est pas disponible Modifie la taille de l\'intervalle de chargement (actuellement %s). Une valeur plus faible peut accélérer le chargement initial des vidéos . - Règler la hauteur par demi-tons musicaux - Pas du tempo Valeur par défaut d’ExoPlayer Nouveaux flux Configurer la notification du flux en cours de lecture diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 52225b843..7b49adc6d 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -672,8 +672,6 @@ Enfileirado Procurar actualizacións Procurar manualmente novas versións - Axustar o ton do semitóns musicais - Paso do tempo A procurar actualizacións… A partir do Android 10, só o \'Sistema de Acceso ao Almacenamento\' está soportado Cambia o tamaño do intervalo de carga (actualmente %s). Un valor menor pode acelerar o carregamento do vídeo. Cambios poden precisar un reinicio do reprodutor. diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index a0e6c32a9..4d4350f07 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -706,11 +706,8 @@ התראת דיווח שגיאה לא נמצאו מנהלי קבצים שמתאימים לפעולה הזאת. \nנא להתקין מנהל קבצים שתומך בתשתית גישה לאחסון. - כבר מתנגן ברקע הערה ננעצה LeakCanary אינה זמינה - התאמת גובה הצליל לפי חצאי טונים מוזיקליים - צעד מקצב ברירת מחדל של ExoPlayer שינוי גודל מרווח הטעינה (כרגע %s). ערך נמוך יותר עשוי להאיץ את טעינת הווידאו הראשונית. שינויים דורשים את הפעלת הנגן מחדש. התראות על תזרימים חדשים להרשמה diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index adaf6a8c0..e5457d3e1 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -683,6 +683,5 @@ %1$s letöltés törölve Rögzített megjegyzés - Már megy a lejátszás a háttérben LeakCanary nem elérhető \ No newline at end of file diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index e9d400be2..d5bc90e6f 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -229,7 +229,6 @@ Aperir con Suggestiones de recerca remote Cargar miniaturas - Notification Monstrante resultatos pro: %s Solmente alicun apparatos pote reproducer videos 2K/4K Initiar le reproductor principal in schermo plen diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 056cdfce4..1a88bb747 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -671,11 +671,8 @@ Tidak ada manajer file yang ditemukan untuk tindakan ini. \nMohon instal sebuah manajer file yang kompatibel dengan Storage Access Framework. Komentar dipin - Sudah diputar di latar belakang LeakCanary tidak tersedia - Langkah tempo Default ExoPlayer - Atur nada berdasarkan semitone musik Ubah ukuran interval pemuatan (saat ini %s). Sebuah nilai yang rendah mungkin dapat membuat pemuatan video awal lebih cepat. Membutuhkan sebuah pemulaian ulang pada pemain. Memuat detail stream… Frekuensi pemeriksaan diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9f7b74d8e..9671f8617 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -683,12 +683,9 @@ Non è stato trovato alcun gestore di file appropriato per questa azione. \nInstallane uno compatibile con Storage Access Framework. Commento in primo piano - Già in riproduzione in sottofondo LeakCanary non è disponibile - Regola il tono secondo i semitoni musicali Predefinito ExoPlayer Cambia la dimensione dell\'intervallo da caricare (attualmente %s). Un valore basso può velocizzare il caricamento iniziale del video. La modifica richiede il riavvio del lettore. - Passo tempo Notifiche di nuove stream dalle iscrizioni Frequenza controllo Richiesta connessione alla rete diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index a0699f229..9a15b25d1 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -663,7 +663,6 @@ エラーが発生しました。通知をご覧ください NewPipe はエラーに遭遇しました。タップして報告 スナックバーにエラーを表示 - 既にバックグラウンドで再生されています 固定されたコメント この動作に適切なファイルマネージャが見つかりませんでした。 \nStorage Access Frameworkと互換性のあるファイルマネージャをインストールしてください。 @@ -673,7 +672,6 @@ エラー通知を作成 エラーを報告する通知 LeakCanaryが利用不可能です - 緩急音階 プレイヤー通知 ストリームの詳細を読み込んでいます… 登録チャンネルの新しいストリームについて通知する diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 92b24fbc8..b2829ba27 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -672,7 +672,6 @@ Viser et krasjalternativ ved bruk av avspilleren Det oppstod en feil. Sjekk merknaden. Festet kommentar - Spilles allerede i bakgrunnen Feilrapport-merknad Merknader for innrapportering av feil NewPipe-feil. Trykk for å rapportere. @@ -682,6 +681,4 @@ Installer en filbehandler som støtter lagringstilgangsrammeverk først. LeakCanary er ikke tilgjengelig ExoPlayer-forvalg - Juster toneart etter musikalske halvtoner - Tempo-steg \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index dd405963a..3a943d2ca 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -677,7 +677,6 @@ NewPipe meldt fout, tik voor bericht Foutmelding Maak een foutmelding - Speelt al op de achtergrond Korte foutmelding weergeven Er is geen geschikte bestandsbeheerder gevonden voor deze actie. \nInstalleer een bestandsbeheerder of probeer \'%s\' uit te schakelen in de download instellingen. @@ -686,7 +685,5 @@ Vastgemaakt commentaar LeakCanary is niet beschikbaar Verander de laad interval tijd (nu %s). Een lagere waarde kan het initiële laden van de video versnellen. De wijziging vereist een herstart van de speler. - Pas de toonhoogte aan met muzikale halve tonen - Tempo stap ExoPlayer standaard \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index cfa995212..6e2c95119 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -701,14 +701,11 @@ \nZainstaluj menedżer plików lub spróbuj wyłączyć „%s” w ustawieniach pobierania.
Nie znaleziono odpowiedniego menedżera plików dla tej akcji. \nZainstaluj menedżer plików zgodny z Storage Access Framework. - Już jest odtwarzane w tle Przypięty komentarz LeakCanary jest niedostępne Rozmiar interwału ładowania odtwarzania Zmień rozmiar interwału ładowania (aktualnie %s). Niższa wartość może przyspieszyć początkowe ładowanie wideo. Zmiany wymagają ponownego uruchomienia odtwarzacza domyślny ExoPlayera - Dostosuj wysokość półtonami - Krok tempa Powiadomienie odtwarzacza Skonfiguruj powiadomienie aktualnie odtwarzanego strumienia Uruchom sprawdzenie nowych strumieni diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 0850034f6..d19196c06 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -682,12 +682,9 @@ Mostrar um snackbar de erro Nenhum gerenciador de arquivos apropriado foi encontrado para esta ação. \nInstale um gerenciador de arquivos ou tente desativar \'%s\' nas configurações de download. - Já está tocando em segundo plano Comentário fixado O LeakCanary não está disponível - Passo do tempo Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. As alterações exigem que o player reinicie. - Ajustar o tom por semitons musicais ExoPlayer padrão Notificação do reprodutor Configurar a notificação do fluxo da reprodução atual diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 43e782251..aa3862b6b 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -683,11 +683,8 @@ Nenhum gestor de ficheiros apropriado foi encontrado para esta ação. \nPor favor, instale um gestor de ficheiros compatível com o Storage Access Framework. Comentário fixado - Já está a reproduzir em segundo plano LeakCanary não está disponível Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar. - Ajustar o tom por semitons musicais - Passo do tempo Predefinido do ExoPlayer Notificações A carregar detalhes do fluxo… diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 9e1a80916..9563c9a6f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -554,7 +554,6 @@ URL não reconhecido. Abrir com outra aplicação\? Enfileiramento automático Embaralhar - Notificação Apenas em Wi-Fi Nada Mudar de um reprodutor para outro pode substituir a sua fila @@ -683,13 +682,10 @@ \nPor favor, instale um gestor de ficheiros ou tente desativar \'%s\' nas configurações de descarregar.
Nenhum gestor de ficheiros apropriado foi encontrado para esta ação. \nPor favor, instale um gestor de ficheiros compatível com o Storage Access Framework. - Já está a reproduzir em segundo plano Comentário fixado LeakCanary não está disponível - Ajustar o tom por semitons musicais Predefinido do ExoPlayer Altere o tamanho do intervalo de carregamento (atualmente %s). Um valor menor pode acelerar o carregamento inicial do vídeo. Se fizer alterações é necessário reiniciar. - Passo do tempo Notificação do reprodutor Configurar a notificação da reprodução do fluxo atual Notificações diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index e83fc5462..67108b500 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -679,7 +679,6 @@ Procesarea.. Poate dura un moment Verifică dacă există actualizări Verifică manual dacă există versiuni noi - Se redă deja pe fundal Comentariu lipit Notificare cu raport de eroare Afișează opțiunea de a întrerupe atunci când utilizați playerul diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fc4c03f42..2a16a4573 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -691,8 +691,6 @@ Проверить обновления Проверка обновлений… Новое на канале - Отчёт об ошибках плеера - Подробные отчёты об ошибках плеера вместо коротких всплывающих сообщений (полезно при диагностике проблем) Уведомления Новые видео Уведомления о новых видео в подписках @@ -718,11 +716,8 @@ \nПожалуйста, установите файловый менеджер, или попробуйте отключить \'%s\' в настройках загрузок.
Для этого действия не найдено подходящего файлового менеджера. \nПожалуйста, установите файловый менеджер, совместимый со Storage Access Framework (SAF). - Уже проигрывается в фоне Закреплённый комментарий LeakCanary недоступна - Регулировка высоты тона по музыкальным полутонам - Шаг темпа Стандартное значение ExoPlayer Изменить размер интервала загрузки (сейчас %s). Меньшее значение может ускорить начальную загрузку видео. Изменение значения потребует перезапуска плеера. Загрузка деталей трансляции… diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 38a7c3303..7c6120832 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -683,10 +683,7 @@ \nPro praghere installa unu gestore de documentos cumpatìbile cun su \"Sistema de Atzessu a s\'Archiviatzione\".
Faghe serrare su riproduidore Cummentu apicadu - Giai in riprodutzione in s\'isfundu LeakCanary no est a disponimentu - Règula s\'intonatzione in base a sos semitonos musicales - Passu de tempus Valore ExoPlayer predefinidu Muda sa mannària de s\'intervallu de carrigamentu (in custu momentu %s). Unu valore prus bassu diat pòdere allestrare su carrigamentu de incumintzu de su vìdeu. Sas modìficas tenent bisòngiu de torrare a allùghere su riproduidore. Cunfigura sa notìfica de su flussu in cursu de riprodutzione diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 9b7a62145..c47562bb0 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -647,8 +647,6 @@ Pri každom sťahovaní sa zobrazí výzva kam uložiť súbor Nie je nastavený adresár na sťahovanie, nastavte ho teraz Označiť ako videné - Načítavanie podrobností o kanáli… - Chyba pri zobrazení podrobností kanála Vypnuté Zapnuté Režim tabletu @@ -698,8 +696,6 @@ Zobrazí možnosť zlyhania pri používaní prehrávača Zobraziť krátke oznámenie chyby Oznámte chybu - Upraviť výšku poltónov - Krok tempa ExoPlayer preddefinovaný Zmeniť interval načítania (aktuálne %s). Menšia hodnota môže zvýšiť rýchlosť prvotného načítania videa. Zmena vyžaduje reštart. Upozornenia diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 63af6d4d6..b7e66a85b 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -682,13 +682,10 @@ \nInstallera en filhanterare eller testa att inaktivera \'%s\' i nedladdningsinställningarna.
Ingen lämplig filhanterare hittades för denna åtgärd. \nInstallera en filhanterare som är kompatibel med Storage Access Framework. - Spelas redan i bakgrunden Fäst kommentar LeakCanary är inte tillgänglig - Justera tonhöjden med musikaliska halvtoner ExoPlayer standard Ändra inläsningsintervallets storlek (för närvarande %s). Ett lägre värde kan påskynda den första videoinläsningen. Ändringar kräver omstart av spelaren. - Temposteg Validera frekvens Kräver nätverksanslutning Alla nätverk diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index d7c920f2b..827da452a 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -253,7 +253,6 @@ இயக்குதலைத் மறுதொடர் நி நிகழ்வு ஏற்கனவே உள்ளது - அறிவிப்பு யூடியூபின் \"கட்டுப்பாடு பயன்முறை\"ஐ இயக்கு பாடல்கள் பிழைகளைப் புகாரளிக்க அறிவிப்புகள் diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 60542e0b6..3958e02b0 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -371,7 +371,6 @@ reCAPTCHA సవాలు reCAPTCHA సవాలు అభ్యర్థించబడింది ప్లేజాబితాను ఎంచుకోండి - ఇప్పటికే వెనుకగా ప్లే అవుతోంది డాటాబేసుని ఎగుమతిచేయుము యాప్ పునఃప్రారంభించబడిన తర్వాత భాష మారుతుంది ఛానెల్ వివరాలను చూపు diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 958dc5eeb..a9bbe30e5 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -682,13 +682,10 @@ Hata raporları için bildirimler Oynatıcı kullanırken çöktürme seçeneği gösterir Oynatıcıyı çöktür - Zaten arka planda oynuyor Sabitlenmiş yorum LeakCanary yok Yükleme ara boyutunu değiştir (şu anda %s). Düşük bir değer videonun ilk yüklenişini hızlandırabilir. Değişiklikler oynatıcının yeniden başlatılmasını gerektirir. ExoPlayer öntanımlısı - Tempo adımı - Perdeyi müzikal yarım tonlarla uyarla Yeni akış bildirimleri Bildirimler diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f2a3e986e..0768f241b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -58,7 +58,6 @@ Завантаження Звіт про помилку Усе - Так Вимкнено Збій застосунку/інтерфейсу Ваш коментар (англійською): @@ -699,11 +698,8 @@ \nУстановіть файловий менеджер, сумісний зі Storage Access Framework.
Показати панель помилок Створити сповіщення про помилку - Уже відтворюється у фоновому режимі Закріплений коментар LeakCanary недоступний - Крок темпу - Регулювання висоти звуку за музичними півтонами Типовий ExoPlayer Змінити розмір інтервалу завантаження (наразі %s). Менше значення може прискорити початкове завантаження відео. Зміни вимагають перезапуску програвача. Ви підписалися на цей канал diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 7ea1a1ed5..08bf7cae0 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -672,8 +672,6 @@ \nVui lòng cài đặt ứng dụng quản lý tệp hoặc tắt \'%s\' trong cài đặt tải xuống.
Thay đổi kích thước khoảng thời gian tải (tầm khoảng %s). Để ở giá trị thấp hơn có thể sẽ tăng tốc độ tải video hơn ban đầu. Khởi động lại trình phát để áp dụng thay đổi. LeakCanary không khả dụng - Điều chỉnh cao độ theo nhạc nền âm nhạc - Nhịp độ tiếp theo ExoPlayer mặc định Bình luận được ghim \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 0e79f5a58..d527edc9b 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -603,10 +603,8 @@ %s 個新加串流 - 按樂音半度調整音高 新加串流通知 係咪要喺磁碟機上面消除晒全部下載咗嘅檔案? 通知已停用 單曲 - 節奏步伐 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index fd129fb6c..8b5a24661 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -539,7 +539,6 @@ 作用中播放器的佇列可能會被取代 從一個播放器切換到另一個可能會取代您的佇列 清除佇列前要求確認 - 通知 正在緩衝 隨機播放 @@ -638,8 +637,6 @@ 拖動列縮圖預覽 被創作者加心號 標記為已看過 - 正在載入頻道詳細資訊…… - 顯示頻道詳細資訊時發生錯誤 在圖片頂部顯示畢卡索彩色絲帶,指示其來源:紅色代表網路、藍色代表磁碟、綠色代表記憶體 顯示圖片指示器 遠端搜尋建議 @@ -674,12 +671,9 @@ NewPipe 遇到錯誤,點擊以回報 發生錯誤,請檢視通知 釘選的留言 - 已經在背景播放 LeakCanary 無法使用 - 步進時間 ExoPlayer 預設值 變更載入間隔大小(目前為 %s)。較低的值可能會提昇初始影片載入速度。變更需要重新啟動播放器。 - 按音樂半音調整音高 播放器通知 通知 正在載入串流詳細資訊…… From a67927c29ce5e3f60ed628fb307bda3333e28774 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 1 May 2022 21:56:49 +0200 Subject: [PATCH 089/992] Fix dialogs having incorrect color when opened via RouterActivity --- app/src/main/res/values/styles.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 7c1265580..894e70bad 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -131,10 +131,6 @@ + + + + + - - + diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml deleted file mode 100644 index 8fa00d0d8..000000000 --- a/app/src/main/res/values-v21/styles.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/colors_services.xml b/app/src/main/res/values/colors_services.xml index d6cd73d52..f3487810a 100644 --- a/app/src/main/res/values/colors_services.xml +++ b/app/src/main/res/values/colors_services.xml @@ -2,53 +2,37 @@ #e53935 - #992722 #000000 - #e53935 #992722 - #7a1717 #FFFFFF - #992722 #f57c00 - #995700 #000000 - #f57c00 #a35300 - #7d4000 #FFFFFF - #a35300 #ff6f00 - #c43e00 #000000 - #ff6f00 #a34700 - #942f00 #FFFFFF - #a34700 #9e9e9e #000000 - #9e9e9e #878787 #FFFFFF - #878787 #17a0c4 #000000 - #17a0c4 #1383a1 #FFFFFF - #1383a1 \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e711b35ab..164f10672 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,27 +1,29 @@ - + + - - - - diff --git a/app/src/main/res/values/styles_services.xml b/app/src/main/res/values/styles_services.xml index ce67f468b..db1fba397 100644 --- a/app/src/main/res/values/styles_services.xml +++ b/app/src/main/res/values/styles_services.xml @@ -1,16 +1,22 @@ - - - From 4ec9cbe379a55579648ec2ac28e14bf5e232db76 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 6 Jul 2022 17:47:12 -0400 Subject: [PATCH 184/992] Remove AndroidX Webkit --- app/build.gradle | 1 - .../org/schabi/newpipe/error/ReCaptchaActivity.java | 12 +++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 67026df7e..9867037e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -216,7 +216,6 @@ dependencies { // Newer version specified to prevent accessibility regressions with RecyclerView, see: // https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01 implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' - implementation 'androidx.webkit:webkit:1.4.0' implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" implementation 'com.google.android.material:material:1.5.0' diff --git a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java index c9f3a82d9..e2780d215 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ReCaptchaActivity.java @@ -8,8 +8,10 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.webkit.CookieManager; +import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; +import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -17,7 +19,6 @@ import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.NavUtils; import androidx.preference.PreferenceManager; -import androidx.webkit.WebViewClientCompat; import org.schabi.newpipe.databinding.ActivityRecaptchaBinding; import org.schabi.newpipe.DownloaderImpl; @@ -85,14 +86,15 @@ public class ReCaptchaActivity extends AppCompatActivity { webSettings.setJavaScriptEnabled(true); webSettings.setUserAgentString(DownloaderImpl.USER_AGENT); - recaptchaBinding.reCaptchaWebView.setWebViewClient(new WebViewClientCompat() { + recaptchaBinding.reCaptchaWebView.setWebViewClient(new WebViewClient() { @Override - public boolean shouldOverrideUrlLoading(final WebView view, final String url) { + public boolean shouldOverrideUrlLoading(final WebView view, + final WebResourceRequest request) { if (MainActivity.DEBUG) { - Log.d(TAG, "shouldOverrideUrlLoading: url=" + url); + Log.d(TAG, "shouldOverrideUrlLoading: url=" + request.getUrl().toString()); } - handleCookiesFromUrl(url); + handleCookiesFromUrl(request.getUrl().toString()); return false; } From 189c92affadf40f3bedd7f52eec1b1813fce98a3 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 6 Jul 2022 17:48:38 -0400 Subject: [PATCH 185/992] More minSdk 21 cleanup --- .../main/java/org/schabi/newpipe/error/ErrorUtil.kt | 8 +------- app/src/main/java/org/schabi/newpipe/ktx/View.kt | 11 +++-------- .../settings/PlayerNotificationSettingsFragment.kt | 10 ---------- .../custom/NotificationActionsPreference.java | 2 +- 4 files changed, 5 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt index e4dd2e16d..86e2e1028 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorUtil.kt @@ -114,13 +114,7 @@ class ErrorUtil { context, context.getString(R.string.error_report_channel_id) ) - .setSmallIcon( - // the vector drawable icon causes crashes on KitKat devices - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - R.drawable.ic_bug_report - else - android.R.drawable.stat_notify_error - ) + .setSmallIcon(R.drawable.ic_bug_report) .setContentTitle(context.getString(R.string.error_report_notification_title)) .setContentText(context.getString(errorInfo.messageStringId)) .setAutoCancel(true) 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 ace1dbf7e..ea680dd60 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -12,7 +12,6 @@ import android.view.View import androidx.annotation.ColorInt import androidx.annotation.FloatRange import androidx.core.animation.addListener -import androidx.core.view.ViewCompat import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -21,10 +20,6 @@ import org.schabi.newpipe.MainActivity private const val TAG = "ViewUtils" -inline var View.backgroundTintListCompat: ColorStateList? - get() = ViewCompat.getBackgroundTintList(this) - set(value) = ViewCompat.setBackgroundTintList(this, value) - /** * Animate the view. * @@ -106,11 +101,11 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo viewPropertyAnimator.interpolator = FastOutSlowInInterpolator() viewPropertyAnimator.duration = duration viewPropertyAnimator.addUpdateListener { animation: ValueAnimator -> - backgroundTintListCompat = ColorStateList(empty, intArrayOf(animation.animatedValue as Int)) + backgroundTintList = ColorStateList(empty, intArrayOf(animation.animatedValue as Int)) } viewPropertyAnimator.addListener( - onCancel = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) }, - onEnd = { backgroundTintListCompat = ColorStateList(empty, intArrayOf(colorEnd)) } + onCancel = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) }, + onEnd = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) } ) viewPropertyAnimator.start() } diff --git a/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt index 3549bff42..7d95433a4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/PlayerNotificationSettingsFragment.kt @@ -1,19 +1,9 @@ package org.schabi.newpipe.settings -import android.os.Build import android.os.Bundle -import androidx.preference.Preference -import org.schabi.newpipe.R class PlayerNotificationSettingsFragment : BasePreferenceFragment() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResourceRegistry() - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - val colorizePref: Preference? = findPreference(getString(R.string.notification_colorize_key)) - colorizePref?.let { - preferenceScreen.removePreference(it) - } - } } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 798d299c0..849574171 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -218,7 +218,7 @@ public class NotificationActionsPreference extends Preference { final int color = ThemeHelper.resolveColorFromAttr(getContext(), android.R.attr.textColorPrimary); drawable = DrawableCompat.wrap(drawable).mutate(); - DrawableCompat.setTint(drawable, color); + drawable.setTint(color); radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, drawable, null); } From 76ced59b62322537eb818cd3fea7ddd1b9c2d711 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 8 Apr 2022 09:35:14 +0200 Subject: [PATCH 186/992] Refactor player: separate UIs and more --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 2 +- .../org/schabi/newpipe/RouterActivity.java | 6 +- .../fragments/detail/VideoDetailFragment.java | 155 +- .../list/channel/ChannelFragment.java | 2 +- .../list/playlist/PlaylistFragment.java | 2 +- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../newpipe/player/NotificationUtil.java | 18 +- .../newpipe/player/PlayQueueActivity.java | 50 +- .../org/schabi/newpipe/player/Player.java | 2654 ++--------------- .../{MainPlayer.java => PlayerService.java} | 102 +- .../player/event/BasePlayerGestureListener.kt | 520 ---- .../player/event/PlayerEventListener.java | 1 - .../player/event/PlayerGestureListener.java | 256 -- .../event/PlayerServiceEventListener.java | 2 + .../PlayerServiceExtendedEventListener.java | 4 +- .../gesture/BasePlayerGestureListener.kt | 182 ++ .../CustomBottomSheetBehavior.java | 2 +- .../{event => gesture}/DisplayPortion.kt | 2 +- .../{event => gesture}/DoubleTapListener.kt | 2 +- .../gesture/MainPlayerGestureListener.kt | 232 ++ .../gesture/PopupPlayerGestureListener.kt | 287 ++ .../helper/PlaybackParameterDialog.java | 4 +- .../newpipe/player/helper/PlayerHelper.java | 61 +- .../newpipe/player/helper/PlayerHolder.java | 23 +- .../view/PlaybackSpeedClickListener.kt | 47 - .../listeners/view/QualityClickListener.kt | 41 - .../player/playback/PlayerMediaSession.java | 5 +- .../newpipe/player/ui/MainPlayerUi.java | 937 ++++++ .../player/ui/NotificationPlayerUi.java | 26 + .../schabi/newpipe/player/ui/PlayerUi.java | 120 + .../newpipe/player/ui/PlayerUiList.java | 36 + .../newpipe/player/ui/PopupPlayerUi.java | 460 +++ .../newpipe/player/ui/VideoPlayerUi.java | 1523 ++++++++++ .../custom/NotificationActionsPreference.java | 4 +- .../schabi/newpipe/util/NavigationHelper.java | 28 +- .../views/player/PlayerFastSeekOverlay.kt | 4 +- app/src/main/res/layout/activity_main.xml | 2 +- 38 files changed, 4242 insertions(+), 3564 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{MainPlayer.java => PlayerService.java} (63%) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt rename app/src/main/java/org/schabi/newpipe/player/{event => gesture}/CustomBottomSheetBehavior.java (98%) rename app/src/main/java/org/schabi/newpipe/player/{event => gesture}/DisplayPortion.kt (65%) rename app/src/main/java/org/schabi/newpipe/player/{event => gesture}/DoubleTapListener.kt (81%) create mode 100644 app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt create mode 100644 app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt delete mode 100644 app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java diff --git a/app/build.gradle b/app/build.gradle index 9867037e6..46eee8d00 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -166,7 +166,7 @@ afterEvaluate { if (!System.properties.containsKey('skipFormatKtlint')) { preDebugBuild.dependsOn formatKtlint } - preDebugBuild.dependsOn runCheckstyle, runKtlint + //preDebugBuild.dependsOn runCheckstyle, runKtlint } sonarqube { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f9c99819c..04e28c1ea 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,7 +44,7 @@ diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 1fe6ce7ec..1194b4068 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -60,7 +60,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.dialog.PlaylistDialog; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; @@ -630,8 +630,8 @@ public class RouterActivity extends AppCompatActivity { } // ...the player is not running or in normal Video-mode/type - final MainPlayer.PlayerType playerType = PlayerHolder.getInstance().getType(); - return playerType == null || playerType == MainPlayer.PlayerType.VIDEO; + final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); + return playerType == null || playerType == PlayerService.PlayerType.MAIN; } private void openAddToPlaylistDialog() { 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 278d472d4..5ecc35034 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 @@ -43,6 +43,7 @@ import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; +import androidx.viewbinding.ViewBinding; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; @@ -77,8 +78,8 @@ import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.MainPlayer; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; @@ -87,6 +88,8 @@ import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.player.ui.MainPlayerUi; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; @@ -106,6 +109,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.TimeUnit; import icepick.State; @@ -202,7 +206,7 @@ public final class VideoDetailFragment private ContentObserver settingsContentObserver; @Nullable - private MainPlayer playerService; + private PlayerService playerService; private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); @@ -211,7 +215,7 @@ public final class VideoDetailFragment //////////////////////////////////////////////////////////////////////////*/ @Override public void onServiceConnected(final Player connectedPlayer, - final MainPlayer connectedPlayerService, + final PlayerService connectedPlayerService, final boolean playAfterConnect) { player = connectedPlayer; playerService = connectedPlayerService; @@ -219,6 +223,7 @@ public final class VideoDetailFragment // It will do nothing if the player is not in fullscreen mode hideSystemUiIfNeeded(); + final Optional playerUi = player.UIs().get(MainPlayerUi.class); if (!player.videoPlayerSelected() && !playAfterConnect) { return; } @@ -227,22 +232,23 @@ public final class VideoDetailFragment // If the video is playing but orientation changed // let's make the video in fullscreen again checkLandscape(); - } else if (player.isFullscreen() && !player.isVerticalVideo() + } else if (playerUi.map(ui -> ui.isFullscreen() && !ui.isVerticalVideo()).orElse(false) // Tablet UI has orientation-independent fullscreen && !DeviceUtils.isTablet(activity)) { // Device is in portrait orientation after rotation but UI is in fullscreen. // Return back to non-fullscreen state - player.toggleFullscreen(); + playerUi.ifPresent(MainPlayerUi::toggleFullscreen); } if (playerIsNotStopped() && player.videoPlayerSelected()) { addVideoPlayerView(); } + //noinspection SimplifyOptionalCallChains if (playAfterConnect || (currentInfo != null && isAutoplayEnabled() - && player.getParentActivity() == null)) { + && !playerUi.isPresent())) { autoPlayEnabled = true; // forcefully start playing openVideoPlayerAutoFullscreen(); } @@ -518,7 +524,7 @@ public final class VideoDetailFragment case R.id.overlay_play_pause_button: if (playerIsNotStopped()) { player.playPause(); - player.hideControls(0, 0); + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); showSystemUi(); } else { autoPlayEnabled = true; // forcefully start playing @@ -583,12 +589,12 @@ public final class VideoDetailFragment if (binding.detailSecondaryControlPanel.getVisibility() == View.GONE) { binding.detailVideoTitleView.setMaxLines(10); animateRotation(binding.detailToggleSecondaryControlsView, - Player.DEFAULT_CONTROLS_DURATION, 180); + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 180); binding.detailSecondaryControlPanel.setVisibility(View.VISIBLE); } else { binding.detailVideoTitleView.setMaxLines(1); animateRotation(binding.detailToggleSecondaryControlsView, - Player.DEFAULT_CONTROLS_DURATION, 0); + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 0); binding.detailSecondaryControlPanel.setVisibility(View.GONE); } // view pager height has changed, update the tab layout @@ -746,7 +752,9 @@ public final class VideoDetailFragment @Override public boolean onKeyDown(final int keyCode) { - return isPlayerAvailable() && player.onKeyDown(keyCode); + return isPlayerAvailable() + && player.UIs().get(VideoPlayerUi.class) + .map(playerUi -> playerUi.onKeyDown(keyCode)).orElse(false); } @Override @@ -756,7 +764,7 @@ public final class VideoDetailFragment } // If we are in fullscreen mode just exit from it via first back press - if (isPlayerAvailable() && player.isFullscreen()) { + if (isFullscreen()) { if (!DeviceUtils.isTablet(activity)) { player.pause(); } @@ -1006,8 +1014,7 @@ public final class VideoDetailFragment getChildFragmentManager().beginTransaction() .replace(R.id.relatedItemsLayout, RelatedItemsFragment.getInstance(info)) .commitAllowingStateLoss(); - binding.relatedItemsLayout.setVisibility( - isPlayerAvailable() && player.isFullscreen() ? View.GONE : View.VISIBLE); + binding.relatedItemsLayout.setVisibility(isFullscreen() ? View.GONE : View.VISIBLE); } } @@ -1087,8 +1094,12 @@ public final class VideoDetailFragment private void toggleFullscreenIfInFullscreenMode() { // If a user watched video inside fullscreen mode and than chose another player // return to non-fullscreen mode - if (isPlayerAvailable() && player.isFullscreen()) { - player.toggleFullscreen(); + if (isPlayerAvailable()) { + player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + if (playerUi.isFullscreen()) { + playerUi.toggleFullscreen(); + } + }); } } @@ -1214,16 +1225,10 @@ public final class VideoDetailFragment } final PlayQueue queue = setupPlayQueueForIntent(false); - - // Video view can have elements visible from popup, - // We hide it here but once it ready the view will be shown in handleIntent() - if (playerService.getView() != null) { - playerService.getView().setVisibility(View.GONE); - } addVideoPlayerView(); final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(), - MainPlayer.class, queue, true, autoPlayEnabled); + PlayerService.class, queue, true, autoPlayEnabled); ContextCompat.startForegroundService(activity, playerIntent); } @@ -1235,8 +1240,8 @@ public final class VideoDetailFragment * be reused in a few milliseconds and the flickering would be annoying. */ private void hideMainPlayerOnLoadingNewStream() { - if (!isPlayerServiceAvailable() - || playerService.getView() == null + //noinspection SimplifyOptionalCallChains + if (!isPlayerServiceAvailable() || !getRoot().isPresent() || !player.videoPlayerSelected()) { return; } @@ -1244,7 +1249,7 @@ public final class VideoDetailFragment removeVideoPlayerView(); if (isAutoplayEnabled()) { playerService.stopForImmediateReusing(); - playerService.getView().setVisibility(View.GONE); + getRoot().ifPresent(view -> view.setVisibility(View.GONE)); } else { playerHolder.stopService(); } @@ -1302,26 +1307,33 @@ public final class VideoDetailFragment } private void addVideoPlayerView() { - if (!isPlayerAvailable() || getView() == null) { + if (!isPlayerAvailable()) { return; } - // Check if viewHolder already contains a child - if (player.getRootView().getParent() != binding.playerPlaceholder) { + final Optional root = player.UIs().get(VideoPlayerUi.class) + .map(VideoPlayerUi::getBinding) + .map(ViewBinding::getRoot); + + // Check if viewHolder already contains a child TODO TODO whaat + /*if (playerService != null + && root.map(View::getParent).orElse(null) != binding.playerPlaceholder) { playerService.removeViewFromParent(); - } + }*/ setHeightThumbnail(); // Prevent from re-adding a view multiple times - if (player.getRootView().getParent() == null) { - binding.playerPlaceholder.addView(player.getRootView()); + if (root.isPresent() && root.get().getParent() == null) { + binding.playerPlaceholder.addView(root.get()); } } private void removeVideoPlayerView() { makeDefaultHeightForVideoPlaceholder(); - playerService.removeViewFromParent(); + if (player != null) { + player.UIs().get(VideoPlayerUi.class).ifPresent(VideoPlayerUi::removeViewFromParent); + } } private void makeDefaultHeightForVideoPlaceholder() { @@ -1362,7 +1374,7 @@ public final class VideoDetailFragment final boolean isPortrait = metrics.heightPixels > metrics.widthPixels; requireView().getViewTreeObserver().removeOnPreDrawListener(preDrawListener); - if (isPlayerAvailable() && player.isFullscreen()) { + if (isFullscreen()) { final int height = (DeviceUtils.isInMultiWindow(activity) ? requireView() : activity.getWindow().getDecorView()).getHeight(); @@ -1387,8 +1399,9 @@ public final class VideoDetailFragment binding.detailThumbnailImageView.setMinimumHeight(newHeight); if (isPlayerAvailable()) { final int maxHeight = (int) (metrics.heightPixels * MAX_PLAYER_HEIGHT); - player.getSurfaceView() - .setHeights(newHeight, player.isFullscreen() ? newHeight : maxHeight); + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> + ui.getBinding().surfaceView.setHeights(newHeight, + ui.isFullscreen() ? newHeight : maxHeight)); } } @@ -1517,7 +1530,7 @@ public final class VideoDetailFragment if (binding.relatedItemsLayout != null) { if (showRelatedItems) { binding.relatedItemsLayout.setVisibility( - isPlayerAvailable() && player.isFullscreen() ? View.GONE : View.INVISIBLE); + isFullscreen() ? View.GONE : View.INVISIBLE); } else { binding.relatedItemsLayout.setVisibility(View.GONE); } @@ -1778,6 +1791,14 @@ public final class VideoDetailFragment // Player event listener //////////////////////////////////////////////////////////////////////////*/ + @Override + public void onViewCreated() { + // Video view can have elements visible from popup, + // We hide it here but once it ready the view will be shown in handleIntent() + getRoot().ifPresent(view -> view.setVisibility(View.GONE)); + addVideoPlayerView(); + } + @Override public void onQueueUpdate(final PlayQueue queue) { playQueue = queue; @@ -1898,15 +1919,10 @@ public final class VideoDetailFragment @Override public void onFullscreenStateChanged(final boolean fullscreen) { setupBrightness(); + //noinspection SimplifyOptionalCallChains if (!isPlayerAndPlayerServiceAvailable() - || playerService.getView() == null - || player.getParentActivity() == null) { - return; - } - - final View view = playerService.getView(); - final ViewGroup parent = (ViewGroup) view.getParent(); - if (parent == null) { + || !player.UIs().get(MainPlayerUi.class).isPresent() + || getRoot().map(View::getParent).orElse(null) == null) { return; } @@ -1934,7 +1950,7 @@ public final class VideoDetailFragment final boolean isLandscape = DeviceUtils.isLandscape(requireContext()); if (DeviceUtils.isTablet(activity) && (!globalScreenOrientationLocked(activity) || isLandscape)) { - player.toggleFullscreen(); + player.UIs().get(MainPlayerUi.class).ifPresent(MainPlayerUi::toggleFullscreen); return; } @@ -2017,7 +2033,7 @@ public final class VideoDetailFragment } activity.getWindow().getDecorView().setSystemUiVisibility(visibility); - if (isInMultiWindow || (isPlayerAvailable() && player.isFullscreen())) { + if (isInMultiWindow || isFullscreen()) { activity.getWindow().setStatusBarColor(Color.TRANSPARENT); activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); } @@ -2026,13 +2042,17 @@ public final class VideoDetailFragment // Listener implementation public void hideSystemUiIfNeeded() { - if (isPlayerAvailable() - && player.isFullscreen() + if (isFullscreen() && bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { hideSystemUi(); } } + private boolean isFullscreen() { + return isPlayerAvailable() && player.UIs().get(VideoPlayerUi.class) + .map(VideoPlayerUi::isFullscreen).orElse(false); + } + private boolean playerIsNotStopped() { return isPlayerAvailable() && !player.isStopped(); } @@ -2055,10 +2075,7 @@ public final class VideoDetailFragment } final WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); - if (!isPlayerAvailable() - || !player.videoPlayerSelected() - || !player.isFullscreen() - || bottomSheetState != BottomSheetBehavior.STATE_EXPANDED) { + if (!isFullscreen() || bottomSheetState != BottomSheetBehavior.STATE_EXPANDED) { // Apply system brightness when the player is not in fullscreen restoreDefaultBrightness(); } else { @@ -2082,7 +2099,7 @@ public final class VideoDetailFragment setAutoPlay(true); } - player.checkLandscape(); + player.UIs().get(MainPlayerUi.class).ifPresent(MainPlayerUi::checkLandscape); // Let's give a user time to look at video information page if video is not playing if (globalScreenOrientationLocked(activity) && !player.isPlaying()) { player.play(); @@ -2309,10 +2326,10 @@ public final class VideoDetailFragment if (DeviceUtils.isLandscape(requireContext()) && isPlayerAvailable() && player.isPlaying() - && !player.isFullscreen() - && !DeviceUtils.isTablet(activity) - && player.videoPlayerSelected()) { - player.toggleFullscreen(); + && !isFullscreen() + && !DeviceUtils.isTablet(activity)) { + player.UIs().get(MainPlayerUi.class) + .ifPresent(MainPlayerUi::toggleFullscreen); } setOverlayLook(binding.appBarLayout, behavior, 1); break; @@ -2325,17 +2342,22 @@ public final class VideoDetailFragment // Re-enable clicks setOverlayElementsClickable(true); if (isPlayerAvailable()) { - player.closeItemsList(); + player.UIs().get(MainPlayerUi.class) + .ifPresent(MainPlayerUi::closeItemsList); } setOverlayLook(binding.appBarLayout, behavior, 0); break; case BottomSheetBehavior.STATE_DRAGGING: case BottomSheetBehavior.STATE_SETTLING: - if (isPlayerAvailable() && player.isFullscreen()) { + if (isFullscreen()) { showSystemUi(); } - if (isPlayerAvailable() && player.isControlsVisible()) { - player.hideControls(0, 0); + if (isPlayerAvailable()) { + player.UIs().get(MainPlayerUi.class).ifPresent(ui -> { + if (ui.isControlsVisible()) { + ui.hideControls(0, 0); + } + }); } break; } @@ -2409,4 +2431,13 @@ public final class VideoDetailFragment boolean isPlayerAndPlayerServiceAvailable() { return (player != null && playerService != null); } + + public Optional getRoot() { + if (player == null) { + return Optional.empty(); + } + + return player.UIs().get(VideoPlayerUi.class) + .map(playerUi -> playerUi.getBinding().getRoot()); + } } 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 fa8f5fdbd..aabd64744 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 @@ -43,7 +43,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.local.feed.notifications.NotificationHelper; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ExtractorHelper; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index ed63c6fd7..65fd8ada1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -43,7 +43,7 @@ import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.util.ExtractorHelper; 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 6023d4b10..3bec07dcc 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 @@ -43,7 +43,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.util.Localization; diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java index 2060d67c4..f5caf2c79 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java @@ -26,14 +26,14 @@ import java.util.List; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE; +import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; +import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; /** * This is a utility class for player notifications. @@ -173,7 +173,7 @@ public final class NotificationUtil { } - void createNotificationAndStartForeground(final Player player, final Service service) { + public void createNotificationAndStartForeground(final Player player, final Service service) { if (notificationBuilder == null) { notificationBuilder = createNotification(player); } 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 676d63458..d00e6265e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -51,7 +51,9 @@ public final class PlayQueueActivity extends AppCompatActivity private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; - protected Player player; + private Player player; + + private PlayQueueAdapter adapter = null; private boolean serviceBound; private ServiceConnection serviceConnection; @@ -132,7 +134,7 @@ public final class PlayQueueActivity extends AppCompatActivity openPlaybackParameterDialog(); return true; case R.id.action_mute: - player.onMuteUnmuteButtonClicked(); + player.toggleMute(); return true; case R.id.action_system_audio: startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS)); @@ -168,7 +170,7 @@ public final class PlayQueueActivity extends AppCompatActivity //////////////////////////////////////////////////////////////////////////// private void bind() { - final Intent bindIntent = new Intent(this, MainPlayer.class); + final Intent bindIntent = new Intent(this, PlayerService.class); final boolean success = bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE); if (!success) { unbindService(serviceConnection); @@ -184,10 +186,7 @@ public final class PlayQueueActivity extends AppCompatActivity player.removeActivityListener(this); } - if (player != null && player.getPlayQueueAdapter() != null) { - player.getPlayQueueAdapter().unsetSelectedListener(); - } - queueControlBinding.playQueue.setAdapter(null); + onQueueUpdate(null); if (itemTouchHelper != null) { itemTouchHelper.attachToRecyclerView(null); } @@ -210,15 +209,15 @@ public final class PlayQueueActivity extends AppCompatActivity if (service instanceof PlayerServiceBinder) { player = ((PlayerServiceBinder) service).getPlayerInstance(); - } else if (service instanceof MainPlayer.LocalBinder) { - player = ((MainPlayer.LocalBinder) service).getPlayer(); + } else if (service instanceof PlayerService.LocalBinder) { + player = ((PlayerService.LocalBinder) service).getPlayer(); } - if (player == null || player.getPlayQueue() == null - || player.getPlayQueueAdapter() == null || player.exoPlayerIsNull()) { + if (player == null || player.getPlayQueue() == null || player.exoPlayerIsNull()) { unbind(); finish(); } else { + onQueueUpdate(player.getPlayQueue()); buildComponents(); if (player != null) { player.setActivityListener(PlayQueueActivity.this); @@ -241,7 +240,6 @@ public final class PlayQueueActivity extends AppCompatActivity private void buildQueue() { queueControlBinding.playQueue.setLayoutManager(new LinearLayoutManager(this)); - queueControlBinding.playQueue.setAdapter(player.getPlayQueueAdapter()); queueControlBinding.playQueue.setClickable(true); queueControlBinding.playQueue.setLongClickable(true); queueControlBinding.playQueue.clearOnScrollListeners(); @@ -249,8 +247,6 @@ public final class PlayQueueActivity extends AppCompatActivity itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper.attachToRecyclerView(queueControlBinding.playQueue); - - player.getPlayQueueAdapter().setSelectedListener(getOnSelectedListener()); } private void buildMetadata() { @@ -370,7 +366,7 @@ public final class PlayQueueActivity extends AppCompatActivity } if (view.getId() == queueControlBinding.controlRepeat.getId()) { - player.onRepeatClicked(); + player.cycleNextRepeatMode(); } else if (view.getId() == queueControlBinding.controlBackward.getId()) { player.playPrevious(); } else if (view.getId() == queueControlBinding.controlFastRewind.getId()) { @@ -382,7 +378,7 @@ public final class PlayQueueActivity extends AppCompatActivity } else if (view.getId() == queueControlBinding.controlForward.getId()) { player.playNext(); } else if (view.getId() == queueControlBinding.controlShuffle.getId()) { - player.onShuffleClicked(); + player.toggleShuffleModeEnabled(); } else if (view.getId() == queueControlBinding.metadata.getId()) { scrollToSelected(); } else if (view.getId() == queueControlBinding.liveSync.getId()) { @@ -445,7 +441,15 @@ public final class PlayQueueActivity extends AppCompatActivity //////////////////////////////////////////////////////////////////////////// @Override - public void onQueueUpdate(final PlayQueue queue) { + public void onQueueUpdate(@Nullable final PlayQueue queue) { + if (queue == null) { + adapter = null; + queueControlBinding.playQueue.setAdapter(null); + } else { + adapter = new PlayQueueAdapter(this, queue); + adapter.setSelectedListener(getOnSelectedListener()); + queueControlBinding.playQueue.setAdapter(adapter); + } } @Override @@ -454,7 +458,6 @@ public final class PlayQueueActivity extends AppCompatActivity onStateChanged(state); onPlayModeChanged(repeatMode, shuffled); onPlaybackParameterChanged(parameters); - onMaybePlaybackAdapterChanged(); onMaybeMuteChanged(); } @@ -582,17 +585,6 @@ public final class PlayQueueActivity extends AppCompatActivity } } - private void onMaybePlaybackAdapterChanged() { - if (player == null) { - return; - } - final PlayQueueAdapter maybeNewAdapter = player.getPlayQueueAdapter(); - if (maybeNewAdapter != null - && queueControlBinding.playQueue.getAdapter() != maybeNewAdapter) { - queueControlBinding.playQueue.setAdapter(maybeNewAdapter); - } - } - private void onMaybeMuteChanged() { if (menu != null && player != null) { final MenuItem item = menu.findItem(R.id.action_mute); 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 100563765..284ab74d8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -24,39 +24,25 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SKIP; import static com.google.android.exoplayer2.Player.DiscontinuityReason; import static com.google.android.exoplayer2.Player.Listener; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static com.google.android.exoplayer2.Player.RepeatMode; -import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; import static org.schabi.newpipe.extractor.ServiceList.YouTube; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; -import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.MainPlayer.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.MainPlayer.ACTION_RECREATE_NOTIFICATION; -import static org.schabi.newpipe.player.MainPlayer.ACTION_REPEAT; -import static org.schabi.newpipe.player.MainPlayer.ACTION_SHUFFLE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; +import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; +import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; -import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; -import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; -import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; -import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; -import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; -import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; -import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; @@ -64,50 +50,17 @@ import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.res.Resources; -import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.media.AudioManager; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.provider.Settings; -import android.util.DisplayMetrics; import android.util.Log; -import android.util.TypedValue; -import android.view.GestureDetector; -import android.view.Gravity; -import android.view.KeyEvent; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.animation.AnticipateInterpolator; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.SeekBar; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -122,13 +75,10 @@ import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.RecyclerView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player.PositionInfo; @@ -139,13 +89,9 @@ import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.text.CueGroup; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector; -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; -import com.google.android.exoplayer2.ui.CaptionStyleCompat; -import com.google.android.exoplayer2.ui.SubtitleView; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.VideoSize; -import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; @@ -153,64 +99,47 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.databinding.PlayerBinding; -import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.extractor.Info; -import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.extractor.stream.StreamSegment; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; -import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; -import org.schabi.newpipe.info_list.StreamSegmentAdapter; -import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.MainPlayer.PlayerType; -import org.schabi.newpipe.player.event.DisplayPortion; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.event.PlayerEventListener; -import org.schabi.newpipe.player.event.PlayerGestureListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.LoadController; import org.schabi.newpipe.player.helper.MediaSessionManager; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; -import org.schabi.newpipe.player.listeners.view.PlaybackSpeedClickListener; -import org.schabi.newpipe.player.listeners.view.QualityClickListener; import org.schabi.newpipe.player.mediaitem.MediaItemTag; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlayerMediaSession; -import org.schabi.newpipe.player.playback.SurfaceHolderCallback; import org.schabi.newpipe.player.playqueue.PlayQueue; -import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; -import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder; -import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder; -import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback; import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; -import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper; -import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder; +import org.schabi.newpipe.player.ui.MainPlayerUi; +import org.schabi.newpipe.player.ui.NotificationPlayerUi; +import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.player.ui.PlayerUiList; +import org.schabi.newpipe.player.ui.PopupPlayerUi; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.StreamTypeUtil; -import org.schabi.newpipe.util.external_communication.KoreUtils; -import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.views.ExpandableSurfaceView; -import org.schabi.newpipe.views.player.PlayerFastSeekOverlay; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -221,14 +150,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.disposables.SerialDisposable; -public final class Player implements - PlaybackListener, - Listener, - SeekBar.OnSeekBarChangeListener, - View.OnClickListener, - PopupMenu.OnMenuItemClickListener, - PopupMenu.OnDismissListener, - View.OnLongClickListener { +public final class Player implements PlaybackListener, Listener { public static final boolean DEBUG = MainActivity.DEBUG; public static final String TAG = Player.class.getSimpleName(); @@ -264,18 +186,12 @@ public final class Player implements public static final int PLAY_PREV_ACTIVATION_LIMIT_MILLIS = 5000; // 5 seconds public static final int PROGRESS_LOOP_INTERVAL_MILLIS = 1000; // 1 second - public static final int DEFAULT_CONTROLS_DURATION = 300; // 300 millis - public static final int DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds - public static final int DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds - public static final int SEEK_OVERLAY_DURATION = 450; // 450 millis /*////////////////////////////////////////////////////////////////////////// // Other constants //////////////////////////////////////////////////////////////////////////*/ - private static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f}; - - private static final int RENDERER_UNAVAILABLE = -1; + public static final int RENDERER_UNAVAILABLE = -1; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -283,8 +199,6 @@ public final class Player implements // play queue might be null e.g. while player is starting @Nullable private PlayQueue playQueue; - private PlayQueueAdapter playQueueAdapter; - private StreamSegmentAdapter segmentAdapter; @Nullable private MediaSourceManager playQueueManager; @@ -299,7 +213,6 @@ public final class Player implements private ExoPlayer simpleExoPlayer; private AudioReactor audioReactor; private MediaSessionManager mediaSessionManager; - @Nullable private SurfaceHolderCallback surfaceHolderCallback; @NonNull private final DefaultTrackSelector trackSelector; @NonNull private final LoadController loadController; @@ -308,13 +221,13 @@ public final class Player implements @NonNull private final VideoPlaybackResolver videoResolver; @NonNull private final AudioPlaybackResolver audioResolver; - private final MainPlayer service; //TODO try to remove and replace everything with context + private final PlayerService service; //TODO try to remove and replace everything with context /*////////////////////////////////////////////////////////////////////////// // Player states //////////////////////////////////////////////////////////////////////////*/ - private PlayerType playerType = PlayerType.VIDEO; + private PlayerType playerType = PlayerType.MAIN; private int currentState = STATE_PREFLIGHT; // audio only mode does not mean that player type is background, but that the player was @@ -322,81 +235,17 @@ public final class Player implements private boolean isAudioOnly = false; private boolean isPrepared = false; private boolean wasPlaying = false; - private boolean isFullscreen = false; - private boolean isVerticalVideo = false; - private boolean fragmentIsVisible = false; - - private List availableStreams; - private int selectedStreamIndex; /*////////////////////////////////////////////////////////////////////////// - // Views + // UIs, listeners and disposables //////////////////////////////////////////////////////////////////////////*/ - private PlayerBinding binding; - - private final Handler controlsVisibilityHandler = new Handler(); - - // fullscreen player - private boolean isQueueVisible = false; - private boolean areSegmentsVisible = false; - private ItemTouchHelper itemTouchHelper; - - /*////////////////////////////////////////////////////////////////////////// - // Popup menus ("popup" means that they pop up, not that they belong to the popup player) - //////////////////////////////////////////////////////////////////////////*/ - - private static final int POPUP_MENU_ID_QUALITY = 69; - private static final int POPUP_MENU_ID_PLAYBACK_SPEED = 79; - private static final int POPUP_MENU_ID_CAPTION = 89; - - private boolean isSomePopupMenuVisible = false; - private PopupMenu qualityPopupMenu; - private PopupMenu playbackSpeedPopupMenu; - private PopupMenu captionPopupMenu; - - /*////////////////////////////////////////////////////////////////////////// - // Popup player - //////////////////////////////////////////////////////////////////////////*/ - - private PlayerPopupCloseOverlayBinding closeOverlayBinding; - - private boolean isPopupClosing = false; - - private float screenWidth; - private float screenHeight; - - /*////////////////////////////////////////////////////////////////////////// - // Popup player window manager - //////////////////////////////////////////////////////////////////////////*/ - - public static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - public static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; - - @Nullable private WindowManager.LayoutParams popupLayoutParams; // null if player is not popup - @Nullable private final WindowManager windowManager; - - /*////////////////////////////////////////////////////////////////////////// - // Gestures - //////////////////////////////////////////////////////////////////////////*/ - - private static final float MAX_GESTURE_LENGTH = 0.75f; - - private int maxGestureLength; // scaled - private GestureDetector gestureDetector; - private PlayerGestureListener playerGestureListener; - - /*////////////////////////////////////////////////////////////////////////// - // Listeners and disposables - //////////////////////////////////////////////////////////////////////////*/ + private final PlayerUiList UIs = new PlayerUiList(); private BroadcastReceiver broadcastReceiver; private IntentFilter intentFilter; - private PlayerServiceEventListener fragmentListener; - private PlayerEventListener activityListener; - private ContentObserver settingsContentObserver; + @Nullable private PlayerServiceEventListener fragmentListener = null; + @Nullable private PlayerEventListener activityListener = null; @NonNull private final SerialDisposable progressUpdateDisposable = new SerialDisposable(); @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable(); @@ -409,16 +258,13 @@ public final class Player implements @NonNull private final SharedPreferences prefs; @NonNull private final HistoryRecordManager recordManager; - @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = - new SeekbarPreviewThumbnailHolder(); - /*////////////////////////////////////////////////////////////////////////// // Constructor //////////////////////////////////////////////////////////////////////////*/ //region Constructor - public Player(@NonNull final MainPlayer service) { + public Player(@NonNull final PlayerService service) { this.service = service; context = service; prefs = PreferenceManager.getDefaultSharedPreferences(context); @@ -434,8 +280,6 @@ public final class Player implements videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); - - windowManager = ContextCompat.getSystemService(context, WindowManager.class); } private VideoPlaybackResolver.QualityResolver getQualityResolver() { @@ -460,235 +304,6 @@ public final class Player implements - /*////////////////////////////////////////////////////////////////////////// - // Setup and initialization - //////////////////////////////////////////////////////////////////////////*/ - //region Setup and initialization - - public void setupFromView(@NonNull final PlayerBinding playerBinding) { - initViews(playerBinding); - if (exoPlayerIsNull()) { - initPlayer(true); - } - initListeners(); - - setupPlayerSeekOverlay(); - } - - private void initViews(@NonNull final PlayerBinding playerBinding) { - binding = playerBinding; - setupSubtitleView(); - - binding.resizeTextView - .setText(PlayerHelper.resizeTypeOf(context, binding.surfaceView.getResizeMode())); - - binding.playbackSeekBar.getThumb() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); - binding.playbackSeekBar.getProgressDrawable() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)); - - final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(getContext(), - R.style.DarkPopupMenu); - - qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView); - playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed); - captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView); - - binding.progressBarLoadingPanel.getIndeterminateDrawable() - .setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)); - - binding.titleTextView.setSelected(true); - binding.channelTextView.setSelected(true); - - // Prevent hiding of bottom sheet via swipe inside queue - binding.itemsList.setNestedScrollingEnabled(false); - } - - private void initPlayer(final boolean playOnReady) { - if (DEBUG) { - Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]"); - } - - simpleExoPlayer = new ExoPlayer.Builder(context, renderFactory) - .setTrackSelector(trackSelector) - .setLoadControl(loadController) - .setUsePlatformDiagnostics(false) - .build(); - simpleExoPlayer.addListener(this); - simpleExoPlayer.setPlayWhenReady(playOnReady); - simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context)); - simpleExoPlayer.setWakeMode(C.WAKE_MODE_NETWORK); - simpleExoPlayer.setHandleAudioBecomingNoisy(true); - - audioReactor = new AudioReactor(context, simpleExoPlayer); - mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, - new PlayerMediaSession(this)); - - registerBroadcastReceiver(); - - // Setup video view - setupVideoSurface(); - - // enable media tunneling - if (DEBUG && PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(context.getString(R.string.disable_media_tunneling_key), false)) { - Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] " - + "media tunneling disabled in debug preferences"); - } else if (DeviceUtils.shouldSupportMediaTunneling()) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setTunnelingEnabled(true)); - } else if (DEBUG) { - Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling"); - } - } - - private void initListeners() { - binding.qualityTextView.setOnClickListener( - new QualityClickListener(this, qualityPopupMenu)); - binding.playbackSpeed.setOnClickListener( - new PlaybackSpeedClickListener(this, playbackSpeedPopupMenu)); - - binding.playbackSeekBar.setOnSeekBarChangeListener(this); - binding.captionTextView.setOnClickListener(this); - binding.resizeTextView.setOnClickListener(this); - binding.playbackLiveSync.setOnClickListener(this); - - playerGestureListener = new PlayerGestureListener(this, service); - gestureDetector = new GestureDetector(context, playerGestureListener); - binding.getRoot().setOnTouchListener(playerGestureListener); - - binding.queueButton.setOnClickListener(v -> onQueueClicked()); - binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked()); - binding.repeatButton.setOnClickListener(v -> onRepeatClicked()); - binding.shuffleButton.setOnClickListener(v -> onShuffleClicked()); - binding.addToPlaylistButton.setOnClickListener(v -> { - if (getParentActivity() != null) { - onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager()); - } - }); - - binding.playPauseButton.setOnClickListener(this); - binding.playPreviousButton.setOnClickListener(this); - binding.playNextButton.setOnClickListener(this); - - binding.moreOptionsButton.setOnClickListener(this); - binding.moreOptionsButton.setOnLongClickListener(this); - binding.share.setOnClickListener(this); - binding.share.setOnLongClickListener(this); - binding.fullScreenButton.setOnClickListener(this); - binding.screenRotationButton.setOnClickListener(this); - binding.playWithKodi.setOnClickListener(this); - binding.openInBrowser.setOnClickListener(this); - binding.playerCloseButton.setOnClickListener(this); - binding.switchMute.setOnClickListener(this); - - settingsContentObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(final boolean selfChange) { - setupScreenRotationButton(); - } - }; - context.getContentResolver().registerContentObserver( - Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, - settingsContentObserver); - binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); - - ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, (view, windowInsets) -> { - final Insets cutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()); - if (!cutout.equals(Insets.NONE)) { - view.setPadding(cutout.left, cutout.top, cutout.right, cutout.bottom); - } - return windowInsets; - }); - - // PlaybackControlRoot already consumed window insets but we should pass them to - // player_overlays and fast_seek_overlay too. Without it they will be off-centered. - binding.playbackControlRoot.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - binding.playerOverlays.setPadding( - v.getPaddingLeft(), - v.getPaddingTop(), - v.getPaddingRight(), - v.getPaddingBottom()); - - // If we added padding to the fast seek overlay, too, it would not go under the - // system ui. Instead we apply negative margins equal to the window insets of - // the opposite side, so that the view covers all of the player (overflowing on - // some sides) and its center coincides with the center of other controls. - final RelativeLayout.LayoutParams fastSeekParams = (RelativeLayout.LayoutParams) - binding.fastSeekOverlay.getLayoutParams(); - fastSeekParams.leftMargin = -v.getPaddingRight(); - fastSeekParams.topMargin = -v.getPaddingBottom(); - fastSeekParams.rightMargin = -v.getPaddingLeft(); - fastSeekParams.bottomMargin = -v.getPaddingTop(); - }); - } - - /** - * Initializes the Fast-For/Backward overlay. - */ - private void setupPlayerSeekOverlay() { - binding.fastSeekOverlay - .seekSecondsSupplier(() -> retrieveSeekDurationFromPreferences(this) / 1000) - .performListener(new PlayerFastSeekOverlay.PerformListener() { - - @Override - public void onDoubleTap() { - animate(binding.fastSeekOverlay, true, SEEK_OVERLAY_DURATION); - } - - @Override - public void onDoubleTapEnd() { - animate(binding.fastSeekOverlay, false, SEEK_OVERLAY_DURATION); - } - - @NonNull - @Override - public FastSeekDirection getFastSeekDirection( - @NonNull final DisplayPortion portion - ) { - if (exoPlayerIsNull()) { - // Abort seeking - playerGestureListener.endMultiDoubleTap(); - return FastSeekDirection.NONE; - } - if (portion == DisplayPortion.LEFT) { - // Check if it's possible to rewind - // Small puffer to eliminate infinite rewind seeking - if (simpleExoPlayer.getCurrentPosition() < 500L) { - return FastSeekDirection.NONE; - } - return FastSeekDirection.BACKWARD; - } else if (portion == DisplayPortion.RIGHT) { - // Check if it's possible to fast-forward - if (currentState == STATE_COMPLETED - || simpleExoPlayer.getCurrentPosition() - >= simpleExoPlayer.getDuration()) { - return FastSeekDirection.NONE; - } - return FastSeekDirection.FORWARD; - } - /* portion == DisplayPortion.MIDDLE */ - return FastSeekDirection.NONE; - } - - @Override - public void seek(final boolean forward) { - playerGestureListener.keepInDoubleTapMode(); - if (forward) { - fastForward(); - } else { - fastRewind(); - } - } - }); - playerGestureListener.doubleTapControls(binding.fastSeekOverlay); - } - - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Playback initialization via intent //////////////////////////////////////////////////////////////////////////*/ @@ -708,6 +323,7 @@ public final class Player implements final PlayerType oldPlayerType = playerType; playerType = retrievePlayerTypeFromIntent(intent); + initUIsForCurrentPlayerType(); // We need to setup audioOnly before super(), see "sourceOf" isAudioOnly = audioPlayerSelected(); @@ -728,9 +344,6 @@ public final class Player implements return; } - // needed for tablets, check the function for a better explanation - directlyOpenFullscreenIfNeeded(); - final PlaybackParameters savedParameters = retrievePlaybackParametersFromPrefs(this); final float playbackSpeed = savedParameters.speed; final float playbackPitch = savedParameters.pitch; @@ -828,46 +441,49 @@ public final class Player implements reloadPlayQueueManager(); } - setupElementsVisibility(); - setupElementsSize(); - - if (audioPlayerSelected()) { - service.removeViewFromParent(); - } else if (popupPlayerSelected()) { - binding.getRoot().setVisibility(View.VISIBLE); - initPopup(); - initPopupCloseOverlay(); - binding.playPauseButton.requestFocus(); - } else { - binding.getRoot().setVisibility(View.VISIBLE); - initVideoPlayer(); - closeItemsList(); - // Android TV: without it focus will frame the whole player - binding.playPauseButton.requestFocus(); - - // Note: This is for automatically playing (when "Resume playback" is off), see #6179 - if (getPlayWhenReady()) { - play(); - } else { - pause(); - } - } + UIs.call(PlayerUi::setupAfterIntent); NavigationHelper.sendPlayerStartedEvent(context); } - /** - * Open fullscreen on tablets where the option to have the main player start automatically in - * fullscreen mode is on. Rotating the device to landscape is already done in {@link - * VideoDetailFragment#openVideoPlayer(boolean)} when the thumbnail is clicked, and that's - * enough for phones, but not for tablets since the mini player can be also shown in landscape. - */ - private void directlyOpenFullscreenIfNeeded() { - if (fragmentListener != null - && PlayerHelper.isStartMainPlayerFullscreenEnabled(service) - && DeviceUtils.isTablet(service) - && videoPlayerSelected() - && PlayerHelper.globalScreenOrientationLocked(service)) { - fragmentListener.onScreenRotationButtonClicked(); + private void initUIsForCurrentPlayerType() { + //noinspection SimplifyOptionalCallChains + if (!UIs.get(NotificationPlayerUi.class).isPresent()) { + UIs.add(new NotificationPlayerUi(this)); + } + + if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) + || (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) { + // correct UI already in place + return; + } + + // try to reuse binding if possible + final PlayerBinding binding = UIs.get(VideoPlayerUi.class).map(VideoPlayerUi::getBinding) + .orElseGet(() -> { + if (playerType == PlayerType.AUDIO) { + return null; + } else { + return PlayerBinding.inflate(LayoutInflater.from(context)); + } + }); + + switch (playerType) { + case MAIN: + UIs.destroyAll(PopupPlayerUi.class); + UIs.add(new MainPlayerUi(this, binding)); + break; + case AUDIO: + UIs.destroyAll(VideoPlayerUi.class); + break; + case POPUP: + UIs.destroyAll(MainPlayerUi.class); + UIs.add(new PopupPlayerUi(this, binding)); + break; + } + + if (fragmentListener != null) { + // make sure UIs know whether a service is connected or not + UIs.call(PlayerUi::onFragmentListenerSet); } } @@ -881,23 +497,55 @@ public final class Player implements destroyPlayer(); initPlayer(playOnReady); setRepeatMode(repeatMode); - // #6825 - Ensure that the shuffle-button is in the correct state on the UI - setShuffleButton(binding.shuffleButton, simpleExoPlayer.getShuffleModeEnabled()); setPlaybackParameters(playbackSpeed, playbackPitch, playbackSkipSilence); playQueue = queue; playQueue.init(); reloadPlayQueueManager(); - if (playQueueAdapter != null) { - playQueueAdapter.dispose(); - } - playQueueAdapter = new PlayQueueAdapter(context, playQueue); - segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener()); + UIs.call(PlayerUi::initPlayback); simpleExoPlayer.setVolume(isMuted ? 0 : 1); notifyQueueUpdateToListeners(); } + + private void initPlayer(final boolean playOnReady) { + if (DEBUG) { + Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]"); + } + + simpleExoPlayer = new ExoPlayer.Builder(context, renderFactory) + .setTrackSelector(trackSelector) + .setLoadControl(loadController) + .setUsePlatformDiagnostics(false) + .build(); + simpleExoPlayer.addListener(this); + simpleExoPlayer.setPlayWhenReady(playOnReady); + simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context)); + simpleExoPlayer.setWakeMode(C.WAKE_MODE_NETWORK); + simpleExoPlayer.setHandleAudioBecomingNoisy(true); + + audioReactor = new AudioReactor(context, simpleExoPlayer); + mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, + new PlayerMediaSession(this)); + + registerBroadcastReceiver(); + + // Setup UIs + UIs.call(PlayerUi::initPlayer); + + // enable media tunneling + if (DEBUG && PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.disable_media_tunneling_key), false)) { + Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] " + + "media tunneling disabled in debug preferences"); + } else if (DeviceUtils.shouldSupportMediaTunneling()) { + trackSelector.setParameters(trackSelector.buildUponParameters() + .setTunnelingEnabled(true)); + } else if (DEBUG) { + Log.d(TAG, "[" + Util.DEVICE_DEBUG_INFO + "] does not support media tunneling"); + } + } //endregion @@ -911,8 +559,7 @@ public final class Player implements if (DEBUG) { Log.d(TAG, "destroyPlayer() called"); } - - cleanupVideoSurface(); + UIs.call(PlayerUi::destroyPlayer); if (!exoPlayerIsNull()) { simpleExoPlayer.removeListener(this); @@ -934,17 +581,17 @@ public final class Player implements if (mediaSessionManager != null) { mediaSessionManager.dispose(); } - - if (playQueueAdapter != null) { - playQueueAdapter.unsetSelectedListener(); - playQueueAdapter.dispose(); - } } public void destroy() { if (DEBUG) { Log.d(TAG, "destroy() called"); } + + saveStreamProgressState(); + setRecovery(); + stopActivityBinding(); + destroyPlayer(); unregisterBroadcastReceiver(); @@ -952,11 +599,7 @@ public final class Player implements progressUpdateDisposable.set(null); PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading - if (binding != null) { - binding.endScreen.setImageBitmap(null); - } - - context.getContentResolver().unregisterContentObserver(settingsContentObserver); + UIs.call(PlayerUi::destroy); } public void setRecovery() { @@ -983,7 +626,7 @@ public final class Player implements playQueue.setRecovery(queuePos, windowPos); } - private void reloadPlayQueueManager() { + public void reloadPlayQueueManager() { if (playQueueManager != null) { playQueueManager.dispose(); } @@ -1002,185 +645,11 @@ public final class Player implements service.stopService(); } - public void smoothStopPlayer() { + public void smoothStopForImmediateReusing() { // Pausing would make transition from one stream to a new stream not smooth, so only stop simpleExoPlayer.stop(); - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Player type specific setup - //////////////////////////////////////////////////////////////////////////*/ - //region Player type specific setup - - private void initVideoPlayer() { - // restore last resize mode - setResizeMode(PlayerHelper.retrieveResizeModeFromPrefs(this)); - binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); - } - - @SuppressLint("RtlHardcoded") - private void initPopup() { - if (DEBUG) { - Log.d(TAG, "initPopup() called"); - } - - // Popup is already added to windowManager - if (popupHasParent()) { - return; - } - - updateScreenSize(); - - popupLayoutParams = retrievePopupLayoutParamsFromPrefs(this); - binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); - - checkPopupPositionBounds(); - - binding.loadingPanel.setMinimumWidth(popupLayoutParams.width); - binding.loadingPanel.setMinimumHeight(popupLayoutParams.height); - - service.removeViewFromParent(); - Objects.requireNonNull(windowManager).addView(binding.getRoot(), popupLayoutParams); - - // Popup doesn't have aspectRatio selector, using FIT automatically - setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); - } - - @SuppressLint("RtlHardcoded") - private void initPopupCloseOverlay() { - if (DEBUG) { - Log.d(TAG, "initPopupCloseOverlay() called"); - } - - // closeOverlayView is already added to windowManager - if (closeOverlayBinding != null) { - return; - } - - closeOverlayBinding = PlayerPopupCloseOverlayBinding.inflate(LayoutInflater.from(context)); - - final WindowManager.LayoutParams closeOverlayLayoutParams = buildCloseOverlayLayoutParams(); - closeOverlayBinding.closeButton.setVisibility(View.GONE); - Objects.requireNonNull(windowManager).addView( - closeOverlayBinding.getRoot(), closeOverlayLayoutParams); - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Elements visibility and size: popup and main players have different look - //////////////////////////////////////////////////////////////////////////*/ - //region Elements visibility and size: popup and main players have different look - - /** - * This method ensures that popup and main players have different look. - * We use one layout for both players and need to decide what to show and what to hide. - * Additional measuring should be done inside {@link #setupElementsSize}. - */ - private void setupElementsVisibility() { - if (popupPlayerSelected()) { - binding.fullScreenButton.setVisibility(View.VISIBLE); - binding.screenRotationButton.setVisibility(View.GONE); - binding.resizeTextView.setVisibility(View.GONE); - binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.GONE); - binding.queueButton.setVisibility(View.GONE); - binding.segmentsButton.setVisibility(View.GONE); - binding.moreOptionsButton.setVisibility(View.GONE); - binding.topControls.setOrientation(LinearLayout.HORIZONTAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.WRAP_CONTENT; - binding.secondaryControls.setAlpha(1.0f); - binding.secondaryControls.setVisibility(View.VISIBLE); - binding.secondaryControls.setTranslationY(0); - binding.share.setVisibility(View.GONE); - binding.playWithKodi.setVisibility(View.GONE); - binding.openInBrowser.setVisibility(View.GONE); - binding.switchMute.setVisibility(View.GONE); - binding.playerCloseButton.setVisibility(View.GONE); - binding.topControls.bringToFront(); - binding.topControls.setClickable(false); - binding.topControls.setFocusable(false); - binding.bottomControls.bringToFront(); - closeItemsList(); - } else if (videoPlayerSelected()) { - binding.fullScreenButton.setVisibility(View.GONE); - setupScreenRotationButton(); - binding.resizeTextView.setVisibility(View.VISIBLE); - binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); - binding.moreOptionsButton.setVisibility(View.VISIBLE); - binding.topControls.setOrientation(LinearLayout.VERTICAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.MATCH_PARENT; - binding.secondaryControls.setVisibility(View.INVISIBLE); - binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context, - R.drawable.ic_expand_more)); - binding.share.setVisibility(View.VISIBLE); - binding.openInBrowser.setVisibility(View.VISIBLE); - binding.switchMute.setVisibility(View.VISIBLE); - binding.playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE); - // Top controls have a large minHeight which is allows to drag the player - // down in fullscreen mode (just larger area to make easy to locate by finger) - binding.topControls.setClickable(true); - binding.topControls.setFocusable(true); - } - showHideKodiButton(); - - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - } - setMuteButton(binding.switchMute, isMuted()); - - animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0); - } - - /** - * Changes padding, size of elements based on player selected right now. - * Popup player has small padding in comparison with the main player - */ - private void setupElementsSize() { - final Resources res = context.getResources(); - final int buttonsMinWidth; - final int playerTopPad; - final int controlsPad; - final int buttonsPad; - - if (popupPlayerSelected()) { - buttonsMinWidth = 0; - playerTopPad = 0; - controlsPad = res.getDimensionPixelSize(R.dimen.player_popup_controls_padding); - buttonsPad = res.getDimensionPixelSize(R.dimen.player_popup_buttons_padding); - } else if (videoPlayerSelected()) { - buttonsMinWidth = res.getDimensionPixelSize(R.dimen.player_main_buttons_min_width); - playerTopPad = res.getDimensionPixelSize(R.dimen.player_main_top_padding); - controlsPad = res.getDimensionPixelSize(R.dimen.player_main_controls_padding); - buttonsPad = res.getDimensionPixelSize(R.dimen.player_main_buttons_padding); - } else { - return; - } - - binding.topControls.setPaddingRelative(controlsPad, playerTopPad, controlsPad, 0); - binding.bottomControls.setPaddingRelative(controlsPad, 0, controlsPad, 0); - binding.qualityTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); - binding.playbackSpeed.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); - binding.playbackSpeed.setMinimumWidth(buttonsMinWidth); - binding.captionTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); - } - - private void showHideKodiButton() { - // show kodi button if it supports the current service and it is enabled in settings - binding.playWithKodi.setVisibility(videoPlayerSelected() - && playQueue != null && playQueue.getItem() != null - && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) - ? View.VISIBLE : View.GONE); + setRecovery(); + UIs.call(PlayerUi::smoothStopForImmediateReusing); } //endregion @@ -1243,11 +712,6 @@ public final class Player implements break; case ACTION_PLAY_PAUSE: playPause(); - if (!fragmentIsVisible) { - // Ensure that we have audio-only stream playing when a user - // started to play from notification's play button from outside of the app - onFragmentStopped(); - } break; case ACTION_PLAY_PREVIOUS: playPrevious(); @@ -1262,55 +726,19 @@ public final class Player implements fastForward(); break; case ACTION_REPEAT: - onRepeatClicked(); + cycleNextRepeatMode(); break; case ACTION_SHUFFLE: - onShuffleClicked(); + toggleShuffleModeEnabled(); break; case ACTION_RECREATE_NOTIFICATION: NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); break; - case VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED: - fragmentIsVisible = true; - useVideoSource(true); - break; - case VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED: - fragmentIsVisible = false; - onFragmentStopped(); - break; case Intent.ACTION_CONFIGURATION_CHANGED: assureCorrectAppLanguage(service); if (DEBUG) { Log.d(TAG, "onConfigurationChanged() called"); } - if (popupPlayerSelected()) { - updateScreenSize(); - changePopupSize(popupLayoutParams.width); - checkPopupPositionBounds(); - } - // Close it because when changing orientation from portrait - // (in fullscreen mode) the size of queue layout can be larger than the screen size - closeItemsList(); - // When the orientation changed, the screen height might be smaller. - // If the end screen thumbnail is not re-scaled, - // it can be larger than the current screen height - // and thus enlarging the whole player. - // This causes the seekbar to be ouf the visible area. - updateEndScreenThumbnail(); - break; - case Intent.ACTION_SCREEN_ON: - // Interrupt playback only when screen turns on - // and user is watching video in popup player. - // Same actions for video player will be handled in ACTION_VIDEO_FRAGMENT_RESUMED - if (popupPlayerSelected() && (isPlaying() || isLoading())) { - useVideoSource(true); - } - break; - case Intent.ACTION_SCREEN_OFF: - // Interrupt playback only when screen turns off with popup player working - if (popupPlayerSelected() && (isPlaying() || isLoading())) { - useVideoSource(false); - } break; case Intent.ACTION_HEADSET_PLUG: //FIXME /*notificationManager.cancel(NOTIFICATION_ID); @@ -1318,6 +746,8 @@ public final class Player implements mediaSessionManager.enable(getBaseContext(), basePlayerImpl.simpleExoPlayer);*/ break; } + + UIs.call(playerUi -> playerUi.onBroadcastReceived(intent)); } private void registerBroadcastReceiver() { @@ -1366,12 +796,12 @@ public final class Player implements NotificationUtil.getInstance() .createNotificationIfNeededAndUpdate(Player.this, false); // there is a new thumbnail, so changed the end screen thumbnail, too. - updateEndScreenThumbnail(); + UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); } @Override public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - Log.e(TAG, "Thumbnail - onBitmapFailed() called with: url = [" + url + "]", e); + Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); currentThumbnail = null; NotificationUtil.getInstance() .createNotificationIfNeededAndUpdate(Player.this, false); @@ -1380,258 +810,11 @@ public final class Player implements @Override public void onPrepareLoad(final Drawable placeHolderDrawable) { if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingStarted() called with: url = [" + url + "]"); + Log.d(TAG, "Thumbnail - onPrepareLoad() called: url = [" + url + "]"); } } }); } - - /** - * Scale the player audio / end screen thumbnail down if necessary. - *

- * This is necessary when the thumbnail's height is larger than the device's height - * and thus is enlarging the player's height - * causing the bottom playback controls to be out of the visible screen. - *

- */ - public void updateEndScreenThumbnail() { - if (currentThumbnail == null) { - return; - } - - final float endScreenHeight = calculateMaxEndScreenThumbnailHeight(); - - final Bitmap endScreenBitmap = Bitmap.createScaledBitmap( - currentThumbnail, - (int) (currentThumbnail.getWidth() - / (currentThumbnail.getHeight() / endScreenHeight)), - (int) endScreenHeight, - true); - - if (DEBUG) { - Log.d(TAG, "Thumbnail - updateEndScreenThumbnail() called with: " - + "currentThumbnail = [" + currentThumbnail + "], " - + currentThumbnail.getWidth() + "x" + currentThumbnail.getHeight() - + ", scaled end screen height = " + endScreenHeight - + ", scaled end screen width = " + endScreenBitmap.getWidth()); - } - - binding.endScreen.setImageBitmap(endScreenBitmap); - } - - /** - * Calculate the maximum allowed height for the {@link R.id.endScreen} - * to prevent it from enlarging the player. - *

- * The calculating follows these rules: - *

    - *
  • - * Show at least stream title and content creator on TVs and tablets - * when in landscape (always the case for TVs) and not in fullscreen mode. - * This requires to have at least 85dp free space for {@link R.id.detail_root} - * and additional space for the stream title text size - * ({@link R.id.detail_title_root_layout}). - * The text size is 15sp on tablets and 16sp on TVs, - * see {@link R.id.titleTextView}. - *
  • - *
  • - * Otherwise, the max thumbnail height is the screen height. - *
  • - *
- * - * @return the maximum height for the end screen thumbnail - */ - private float calculateMaxEndScreenThumbnailHeight() { - // ensure that screenHeight is initialized and thus not 0 - updateScreenSize(); - - if (DeviceUtils.isTv(context) && !isFullscreen) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context); - return Math.min(currentThumbnail.getHeight(), screenHeight - videoInfoHeight); - } else if (DeviceUtils.isTablet(context) && service.isLandscape() && !isFullscreen) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context); - return Math.min(currentThumbnail.getHeight(), screenHeight - videoInfoHeight); - } else { // fullscreen player: max height is the device height - return Math.min(currentThumbnail.getHeight(), screenHeight); - } - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Popup player utils - //////////////////////////////////////////////////////////////////////////*/ - //region Popup player utils - - /** - * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary - * that goes from (0, 0) to (screenWidth, screenHeight). - *

- * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed - * and {@code true} is returned to represent this change. - *

- */ - public void checkPopupPositionBounds() { - if (DEBUG) { - Log.d(TAG, "checkPopupPositionBounds() called with: " - + "screenWidth = [" + screenWidth + "], " - + "screenHeight = [" + screenHeight + "]"); - } - if (popupLayoutParams == null) { - return; - } - - if (popupLayoutParams.x < 0) { - popupLayoutParams.x = 0; - } else if (popupLayoutParams.x > screenWidth - popupLayoutParams.width) { - popupLayoutParams.x = (int) (screenWidth - popupLayoutParams.width); - } - - if (popupLayoutParams.y < 0) { - popupLayoutParams.y = 0; - } else if (popupLayoutParams.y > screenHeight - popupLayoutParams.height) { - popupLayoutParams.y = (int) (screenHeight - popupLayoutParams.height); - } - } - - public void updateScreenSize() { - if (windowManager != null) { - final DisplayMetrics metrics = new DisplayMetrics(); - windowManager.getDefaultDisplay().getMetrics(metrics); - - screenWidth = metrics.widthPixels; - screenHeight = metrics.heightPixels; - if (DEBUG) { - Log.d(TAG, "updateScreenSize() called: screenWidth = [" - + screenWidth + "], screenHeight = [" + screenHeight + "]"); - } - } - } - - /** - * Changes the size of the popup based on the width. - * @param width the new width, height is calculated with - * {@link PlayerHelper#getMinimumVideoHeight(float)} - */ - public void changePopupSize(final int width) { - if (DEBUG) { - Log.d(TAG, "changePopupSize() called with: width = [" + width + "]"); - } - - if (anyPopupViewIsNull()) { - return; - } - - final float minimumWidth = context.getResources().getDimension(R.dimen.popup_minimum_width); - final int actualWidth = (int) (width > screenWidth ? screenWidth - : (width < minimumWidth ? minimumWidth : width)); - final int actualHeight = (int) getMinimumVideoHeight(width); - if (DEBUG) { - Log.d(TAG, "updatePopupSize() updated values:" - + " width = [" + actualWidth + "], height = [" + actualHeight + "]"); - } - - popupLayoutParams.width = actualWidth; - popupLayoutParams.height = actualHeight; - binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); - Objects.requireNonNull(windowManager) - .updateViewLayout(binding.getRoot(), popupLayoutParams); - } - - private void changePopupWindowFlags(final int flags) { - if (DEBUG) { - Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); - } - - if (!anyPopupViewIsNull()) { - popupLayoutParams.flags = flags; - Objects.requireNonNull(windowManager) - .updateViewLayout(binding.getRoot(), popupLayoutParams); - } - } - - public void closePopup() { - if (DEBUG) { - Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing); - } - if (isPopupClosing) { - return; - } - isPopupClosing = true; - - saveStreamProgressState(); - Objects.requireNonNull(windowManager).removeView(binding.getRoot()); - - animatePopupOverlayAndFinishService(); - } - - public void removePopupFromView() { - if (windowManager != null) { - // wrap in try-catch since it could sometimes generate errors randomly - try { - if (popupHasParent()) { - windowManager.removeView(binding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup from window manager", e); - } - - try { - final boolean closeOverlayHasParent = closeOverlayBinding != null - && closeOverlayBinding.getRoot().getParent() != null; - if (closeOverlayHasParent) { - windowManager.removeView(closeOverlayBinding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup overlay from window manager", e); - } - } - } - - private void animatePopupOverlayAndFinishService() { - final int targetTranslationY = - (int) (closeOverlayBinding.closeButton.getRootView().getHeight() - - closeOverlayBinding.closeButton.getY()); - - closeOverlayBinding.closeButton.animate().setListener(null).cancel(); - closeOverlayBinding.closeButton.animate() - .setInterpolator(new AnticipateInterpolator()) - .translationY(targetTranslationY) - .setDuration(400) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationCancel(final Animator animation) { - end(); - } - - @Override - public void onAnimationEnd(final Animator animation) { - end(); - } - - private void end() { - Objects.requireNonNull(windowManager) - .removeView(closeOverlayBinding.getRoot()); - closeOverlayBinding = null; - service.stopService(); - } - }).start(); - } - - private boolean popupHasParent() { - return binding != null - && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams - && binding.getRoot().getParent() != null; - } - - private boolean anyPopupViewIsNull() { - // TODO understand why checking getParentActivity() != null - return popupLayoutParams == null || windowManager == null - || getParentActivity() != null || binding.getRoot().getParent() == null; - } //endregion @@ -1645,7 +828,7 @@ public final class Player implements return getPlaybackParameters().speed; } - private void setPlaybackSpeed(final float speed) { + public void setPlaybackSpeed(final float speed) { setPlaybackParameters(speed, getPlaybackPitch(), getPlaybackSkipSilence()); } @@ -1694,40 +877,13 @@ public final class Player implements private void onUpdateProgress(final int currentProgress, final int duration, final int bufferPercent) { - if (!isPrepared) { - return; - } - - if (duration != binding.playbackSeekBar.getMax()) { - setVideoDurationToControls(duration); - } - if (currentState != STATE_PAUSED) { - updatePlayBackElementsCurrentDuration(currentProgress); - } - if (simpleExoPlayer.isLoading() || bufferPercent > 90) { - binding.playbackSeekBar.setSecondaryProgress( - (int) (binding.playbackSeekBar.getMax() * ((float) bufferPercent / 100))); - } - if (DEBUG && bufferPercent % 20 == 0) { //Limit log - Log.d(TAG, "notifyProgressUpdateToListeners() called with: " - + "isVisible = " + isControlsVisible() + ", " - + "currentProgress = [" + currentProgress + "], " - + "duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]"); - } - binding.playbackLiveSync.setClickable(!isLiveEdge()); - - notifyProgressUpdateToListeners(currentProgress, duration, bufferPercent); - - if (areSegmentsVisible) { - segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress)); - } - - if (isQueueVisible) { - updateQueueTime(currentProgress); + if (isPrepared) { + UIs.call(ui -> ui.onUpdateProgress(currentProgress, duration, bufferPercent)); + notifyProgressUpdateToListeners(currentProgress, duration, bufferPercent); } } - private void startProgressLoop() { + public void startProgressLoop() { progressUpdateDisposable.set(getProgressUpdateDisposable()); } @@ -1735,11 +891,11 @@ public final class Player implements progressUpdateDisposable.set(null); } - private boolean isProgressLoopRunning() { + public boolean isProgressLoopRunning() { return progressUpdateDisposable.get() != null; } - private void triggerProgressUpdate() { + public void triggerProgressUpdate() { if (exoPlayerIsNull()) { return; } @@ -1756,228 +912,12 @@ public final class Player implements error -> Log.e(TAG, "Progress update failure: ", error)); } - @Override // seekbar listener - public void onProgressChanged(final SeekBar seekBar, final int progress, - final boolean fromUser) { - // Currently we don't need method execution when fromUser is false - if (!fromUser) { - return; - } - if (DEBUG) { - Log.d(TAG, "onProgressChanged() called with: " - + "seekBar = [" + seekBar + "], progress = [" + progress + "]"); - } - - binding.currentDisplaySeek.setText(getTimeString(progress)); - - // Seekbar Preview Thumbnail - SeekbarPreviewThumbnailHelper - .tryResizeAndSetSeekbarPreviewThumbnail( - getContext(), - seekbarPreviewThumbnailHolder.getBitmapAt(progress), - binding.currentSeekbarPreviewThumbnail, - binding.subtitleView::getWidth); - - adjustSeekbarPreviewContainer(); - } - - private void adjustSeekbarPreviewContainer() { - try { - // Should only be required when an error occurred before - // and the layout was positioned in the center - binding.bottomSeekbarPreviewLayout.setGravity(Gravity.NO_GRAVITY); - - // Calculate the current left position of seekbar progress in px - // More info: https://stackoverflow.com/q/20493577 - final int currentSeekbarLeft = - binding.playbackSeekBar.getLeft() - + binding.playbackSeekBar.getPaddingLeft() - + binding.playbackSeekBar.getThumb().getBounds().left; - - // Calculate the (unchecked) left position of the container - final int uncheckedContainerLeft = - currentSeekbarLeft - (binding.seekbarPreviewContainer.getWidth() / 2); - - // Fix the position so it's within the boundaries - final int checkedContainerLeft = - Math.max( - Math.min( - uncheckedContainerLeft, - // Max left - binding.playbackWindowRoot.getWidth() - - binding.seekbarPreviewContainer.getWidth() - ), - 0 // Min left - ); - - // See also: https://stackoverflow.com/a/23249734 - final LinearLayout.LayoutParams params = - new LinearLayout.LayoutParams( - binding.seekbarPreviewContainer.getLayoutParams()); - params.setMarginStart(checkedContainerLeft); - binding.seekbarPreviewContainer.setLayoutParams(params); - } catch (final Exception ex) { - Log.e(TAG, "Failed to adjust seekbarPreviewContainer", ex); - // Fallback - position in the middle - binding.bottomSeekbarPreviewLayout.setGravity(Gravity.CENTER); - } - } - - @Override // seekbar listener - public void onStartTrackingTouch(final SeekBar seekBar) { - if (DEBUG) { - Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]"); - } - if (currentState != STATE_PAUSED_SEEK) { - changeState(STATE_PAUSED_SEEK); - } - - saveWasPlaying(); - if (isPlaying()) { - simpleExoPlayer.pause(); - } - - showControls(0); - animate(binding.currentDisplaySeek, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SCALE_AND_ALPHA); - animate(binding.currentSeekbarPreviewThumbnail, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SCALE_AND_ALPHA); - } - - @Override // seekbar listener - public void onStopTrackingTouch(final SeekBar seekBar) { - if (DEBUG) { - Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]"); - } - - seekTo(seekBar.getProgress()); - if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) { - simpleExoPlayer.play(); - } - - binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); - animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); - animate(binding.currentSeekbarPreviewThumbnail, false, 200, AnimationType.SCALE_AND_ALPHA); - - if (currentState == STATE_PAUSED_SEEK) { - changeState(STATE_BUFFERING); - } - if (!isProgressLoopRunning()) { - startProgressLoop(); - } - if (wasPlaying) { - showControlsThenHide(); - } - } - public void saveWasPlaying() { this.wasPlaying = getPlayWhenReady(); } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// - // Controls showing / hiding - //////////////////////////////////////////////////////////////////////////*/ - //region Controls showing / hiding - - public boolean isControlsVisible() { - return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE; - } - - public void showControlsThenHide() { - if (DEBUG) { - Log.d(TAG, "showControlsThenHide() called"); - } - showOrHideButtons(); - showSystemUIPartially(); - - final int hideTime = binding.playbackControlRoot.isInTouchMode() - ? DEFAULT_CONTROLS_HIDE_TIME - : DPAD_CONTROLS_HIDE_TIME; - - showHideShadow(true, DEFAULT_CONTROLS_DURATION); - animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, - AnimationType.ALPHA, 0, () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime)); - } - - public void showControls(final long duration) { - if (DEBUG) { - Log.d(TAG, "showControls() called"); - } - showOrHideButtons(); - showSystemUIPartially(); - controlsVisibilityHandler.removeCallbacksAndMessages(null); - showHideShadow(true, duration); - animate(binding.playbackControlRoot, true, duration); - } - - public void hideControls(final long duration, final long delay) { - if (DEBUG) { - Log.d(TAG, "hideControls() called with: duration = [" + duration - + "], delay = [" + delay + "]"); - } - - showOrHideButtons(); - - controlsVisibilityHandler.removeCallbacksAndMessages(null); - controlsVisibilityHandler.postDelayed(() -> { - showHideShadow(false, duration); - animate(binding.playbackControlRoot, false, duration, AnimationType.ALPHA, - 0, this::hideSystemUIIfNeeded); - }, delay); - } - - public void showHideShadow(final boolean show, final long duration) { - animate(binding.playbackControlsShadow, show, duration, AnimationType.ALPHA, 0, null); - animate(binding.playerTopShadow, show, duration, AnimationType.ALPHA, 0, null); - animate(binding.playerBottomShadow, show, duration, AnimationType.ALPHA, 0, null); - } - - private void showOrHideButtons() { - if (playQueue == null) { - return; - } - - final boolean showPrev = playQueue.getIndex() != 0; - final boolean showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size(); - final boolean showQueue = playQueue.getStreams().size() > 1 && !popupPlayerSelected(); - /* only when stream has segments and is not playing in popup player */ - final boolean showSegment = !popupPlayerSelected() - && !getCurrentStreamInfo() - .map(StreamInfo::getStreamSegments) - .map(List::isEmpty) - .orElse(/*no stream info=*/true); - - binding.playPreviousButton.setVisibility(showPrev ? View.VISIBLE : View.INVISIBLE); - binding.playPreviousButton.setAlpha(showPrev ? 1.0f : 0.0f); - binding.playNextButton.setVisibility(showNext ? View.VISIBLE : View.INVISIBLE); - binding.playNextButton.setAlpha(showNext ? 1.0f : 0.0f); - binding.queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE); - binding.queueButton.setAlpha(showQueue ? 1.0f : 0.0f); - binding.segmentsButton.setVisibility(showSegment ? View.VISIBLE : View.GONE); - binding.segmentsButton.setAlpha(showSegment ? 1.0f : 0.0f); - } - - private void showSystemUIPartially() { - final AppCompatActivity activity = getParentActivity(); - if (isFullscreen && activity != null) { - activity.getWindow().setStatusBarColor(Color.TRANSPARENT); - activity.getWindow().setNavigationBarColor(Color.TRANSPARENT); - - final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - activity.getWindow().getDecorView().setSystemUiVisibility(visibility); - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - } - } - - private void hideSystemUIIfNeeded() { - if (fragmentListener != null) { - fragmentListener.hideSystemUiIfNeeded(); - } + public boolean wasPlaying() { + return wasPlaying; } //endregion @@ -2011,7 +951,7 @@ public final class Player implements private void updatePlaybackState(final boolean playWhenReady, final int playbackState) { if (DEBUG) { - Log.d(TAG, "ExoPlayer - onPlayerStateChanged() called with: " + Log.d(TAG, "ExoPlayer - updatePlaybackState() called with: " + "playWhenReady = [" + playWhenReady + "], " + "playbackState = [" + playbackState + "]"); } @@ -2122,9 +1062,7 @@ public final class Player implements Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); } - setVideoDurationToControls((int) simpleExoPlayer.getDuration()); - - binding.playbackSpeed.setText(formatSpeed(getPlaybackSpeed())); + UIs.call(PlayerUi::onPrepared); if (playWhenReady) { audioReactor.requestAudioFocus(); @@ -2139,20 +1077,7 @@ public final class Player implements startProgressLoop(); } - // if we are e.g. switching players, hide controls - hideControls(DEFAULT_CONTROLS_DURATION, 0); - - binding.playbackSeekBar.setEnabled(false); - binding.playbackSeekBar.getThumb() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); - - binding.loadingPanel.setBackgroundColor(Color.BLACK); - animate(binding.loadingPanel, true, 0); - animate(binding.surfaceForeground, true, 100); - - binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); - animatePlayButtons(false, 100); - binding.getRoot().setKeepScreenOn(false); + UIs.call(PlayerUi::onBlocked); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @@ -2165,28 +1090,7 @@ public final class Player implements startProgressLoop(); } - updateStreamRelatedViews(); - - binding.playbackSeekBar.setEnabled(true); - binding.playbackSeekBar.getThumb() - .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); - - binding.loadingPanel.setVisibility(View.GONE); - - animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); - - animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, - () -> { - binding.playPauseButton.setImageResource(R.drawable.ic_pause); - animatePlayButtons(true, 200); - if (!isQueueVisible) { - binding.playPauseButton.requestFocus(); - } - }); - - changePopupWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS); - checkLandscape(); - binding.getRoot().setKeepScreenOn(true); + UIs.call(PlayerUi::onPlaying); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @@ -2195,10 +1099,8 @@ public final class Player implements if (DEBUG) { Log.d(TAG, "onBuffering() called"); } - binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT); - binding.loadingPanel.setVisibility(View.VISIBLE); - binding.getRoot().setKeepScreenOn(true); + UIs.call(PlayerUi::onBuffering); if (NotificationUtil.getInstance().shouldUpdateBufferingSlot()) { NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); @@ -2214,22 +1116,7 @@ public final class Player implements stopProgressLoop(); } - // Don't let UI elements popup during double tap seeking. This state is entered sometimes - // during seeking/loading. This if-else check ensures that the controls aren't popping up. - if (!playerGestureListener.isDoubleTapping()) { - showControls(400); - binding.loadingPanel.setVisibility(View.GONE); - - animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, - () -> { - binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); - animatePlayButtons(true, 200); - if (!isQueueVisible) { - binding.playPauseButton.requestFocus(); - } - }); - } - changePopupWindowFlags(IDLE_WINDOW_FLAGS); + UIs.call(PlayerUi::onPaused); // Remove running notification when user does not want minimization to background or popup if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE @@ -2238,8 +1125,6 @@ public final class Player implements } else { NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } - - binding.getRoot().setKeepScreenOn(false); } private void onPausedSeek() { @@ -2247,8 +1132,7 @@ public final class Player implements Log.d(TAG, "onPausedSeek() called"); } - animatePlayButtons(false, 100); - binding.getRoot().setKeepScreenOn(true); + UIs.call(PlayerUi::onPausedSeek); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @@ -2261,19 +1145,8 @@ public final class Player implements return; } - animate(binding.playPauseButton, false, 0, AnimationType.SCALE_AND_ALPHA, 0, - () -> { - binding.playPauseButton.setImageResource(R.drawable.ic_replay); - animatePlayButtons(true, DEFAULT_CONTROLS_DURATION); - }); - - binding.getRoot().setKeepScreenOn(false); - changePopupWindowFlags(IDLE_WINDOW_FLAGS); - + UIs.call(PlayerUi::onCompleted); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - if (isFullscreen) { - toggleFullscreen(); - } if (playQueue.getIndex() < playQueue.size() - 1) { playQueue.offsetIndex(+1); @@ -2281,38 +1154,6 @@ public final class Player implements if (isProgressLoopRunning()) { stopProgressLoop(); } - - // When a (short) video ends the elements have to display the correct values - see #6180 - updatePlayBackElementsCurrentDuration(binding.playbackSeekBar.getMax()); - - showControls(500); - animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); - binding.loadingPanel.setVisibility(View.GONE); - animate(binding.surfaceForeground, true, 100); - } - - private void animatePlayButtons(final boolean show, final int duration) { - animate(binding.playPauseButton, show, duration, AnimationType.SCALE_AND_ALPHA); - - boolean showQueueButtons = show; - if (playQueue == null) { - showQueueButtons = false; - } - - if (!showQueueButtons || playQueue.getIndex() > 0) { - animate( - binding.playPreviousButton, - showQueueButtons, - duration, - AnimationType.SCALE_AND_ALPHA); - } - if (!showQueueButtons || playQueue.getIndex() + 1 < playQueue.getStreams().size()) { - animate( - binding.playNextButton, - showQueueButtons, - duration, - AnimationType.SCALE_AND_ALPHA); - } } //endregion @@ -2323,34 +1164,20 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ //region Repeat and shuffle - public void onRepeatClicked() { - if (DEBUG) { - Log.d(TAG, "onRepeatClicked() called"); - } - setRepeatMode(nextRepeatMode(getRepeatMode())); - } - - public void onShuffleClicked() { - if (DEBUG) { - Log.d(TAG, "onShuffleClicked() called"); - } - - if (exoPlayerIsNull()) { - return; - } - simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); - } - @RepeatMode public int getRepeatMode() { return exoPlayerIsNull() ? REPEAT_MODE_OFF : simpleExoPlayer.getRepeatMode(); } - private void setRepeatMode(@RepeatMode final int repeatMode) { + public void setRepeatMode(@RepeatMode final int repeatMode) { if (!exoPlayerIsNull()) { simpleExoPlayer.setRepeatMode(repeatMode); } } + + public void cycleNextRepeatMode() { + setRepeatMode(nextRepeatMode(getRepeatMode())); + } @Override public void onRepeatModeChanged(@RepeatMode final int repeatMode) { @@ -2358,7 +1185,7 @@ public final class Player implements Log.d(TAG, "ExoPlayer - onRepeatModeChanged() called with: " + "repeatMode = [" + repeatMode + "]"); } - setRepeatModeButton(binding.repeatButton, repeatMode); + UIs.call(playerUi -> playerUi.onRepeatModeChanged(repeatMode)); onShuffleOrRepeatModeChanged(); } @@ -2377,39 +1204,26 @@ public final class Player implements } } - setShuffleButton(binding.shuffleButton, shuffleModeEnabled); + UIs.call(playerUi -> playerUi.onShuffleModeEnabledChanged(shuffleModeEnabled)); onShuffleOrRepeatModeChanged(); } + + public void toggleShuffleModeEnabled() { + if (!exoPlayerIsNull()) { + simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); + } + } private void onShuffleOrRepeatModeChanged() { notifyPlaybackUpdateToListeners(); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } - - private void setRepeatModeButton(final AppCompatImageButton imageButton, - @RepeatMode final int repeatMode) { - switch (repeatMode) { - case REPEAT_MODE_OFF: - imageButton.setImageResource(R.drawable.exo_controls_repeat_off); - break; - case REPEAT_MODE_ONE: - imageButton.setImageResource(R.drawable.exo_controls_repeat_one); - break; - case REPEAT_MODE_ALL: - imageButton.setImageResource(R.drawable.exo_controls_repeat_all); - break; - } - } - - private void setShuffleButton(@NonNull final ImageButton button, final boolean shuffled) { - button.setImageAlpha(shuffled ? 255 : 77); - } //endregion /*////////////////////////////////////////////////////////////////////////// - // Playlist append + // Playlist append TODO this does not make sense here //////////////////////////////////////////////////////////////////////////*/ //region Playlist append @@ -2439,23 +1253,16 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ //region Mute / Unmute - public void onMuteUnmuteButtonClicked() { - if (DEBUG) { - Log.d(TAG, "onMuteUnmuteButtonClicked() called"); - } - simpleExoPlayer.setVolume(isMuted() ? 1 : 0); + public void toggleMute() { + final boolean wasMuted = isMuted(); + simpleExoPlayer.setVolume(wasMuted ? 1 : 0); + UIs.call(playerUi -> playerUi.onMuteUnmuteChanged(!wasMuted)); notifyPlaybackUpdateToListeners(); - setMuteButton(binding.switchMute, isMuted()); } - boolean isMuted() { + public boolean isMuted() { return !exoPlayerIsNull() && simpleExoPlayer.getVolume() == 0; } - - private void setMuteButton(@NonNull final ImageButton button, final boolean isMuted) { - button.setImageDrawable(AppCompatResources.getDrawable(context, isMuted - ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); - } //endregion @@ -2519,7 +1326,7 @@ public final class Player implements Log.d(TAG, "ExoPlayer - onTracksChanged(), " + "track group size = " + tracks.getGroups().size()); } - onTextTracksChanged(tracks); + UIs.call(playerUi -> playerUi.onTextTracksChanged(tracks)); } @Override @@ -2528,7 +1335,7 @@ public final class Player implements Log.d(TAG, "ExoPlayer - playbackParameters(), speed = [" + playbackParameters.speed + "], pitch = [" + playbackParameters.pitch + "]"); } - binding.playbackSpeed.setText(formatSpeed(playbackParameters.speed)); + UIs.call(playerUi -> playerUi.onPlaybackParametersChanged(playbackParameters)); } @Override @@ -2580,13 +1387,12 @@ public final class Player implements @Override public void onRenderedFirstFrame() { - //TODO check if this causes black screen when switching to fullscreen - animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION); + UIs.call(PlayerUi::onRenderedFirstFrame); } @Override public void onCues(@NonNull final CueGroup cueGroup) { - binding.subtitleView.setCues(cueGroup.cues); + UIs.call(playerUi -> playerUi.onCues(cueGroup.cues)); } //endregion @@ -2627,7 +1433,7 @@ public final class Player implements // Any error code not explicitly covered here are either unrelated to NewPipe use case // (e.g. DRM) or not recoverable (e.g. Decoder error). In both cases, the player should // shutdown. - @SuppressLint("SwitchIntDef") + @SuppressWarnings("SwitchIntDef") @Override public void onPlayerError(@NonNull final PlaybackException error) { Log.e(TAG, "ExoPlayer - onPlayerError() called with:", error); @@ -2706,18 +1512,6 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ //region Playback position and seek - /** - * Sets the current duration into the corresponding elements. - * @param currentProgress - */ - private void updatePlayBackElementsCurrentDuration(final int currentProgress) { - // Don't set seekbar progress while user is seeking - if (currentState != STATE_PAUSED_SEEK) { - binding.playbackSeekBar.setProgress(currentProgress); - } - binding.playbackCurrentTime.setText(getTimeString(currentProgress)); - } - @Override // own playback listener (this is a getter) public boolean isApproachingPlaybackEdge(final long timeToEndMillis) { // If live, then not near playback edge @@ -2835,20 +1629,6 @@ public final class Player implements simpleExoPlayer.seekToDefaultPosition(); } } - - /** - * Sets the video duration time into all control components (e.g. seekbar). - * @param duration - */ - private void setVideoDurationToControls(final int duration) { - binding.playbackEndTime.setText(getTimeString(duration)); - - binding.playbackSeekBar.setMax(duration); - // This is important for Android TVs otherwise it would apply the default from - // setMax/Min methods which is (max - min) / 20 - binding.playbackSeekBar.setKeyProgressIncrement( - PlayerHelper.retrieveSeekDurationFromPreferences(this)); - } //endregion @@ -2972,6 +1752,7 @@ public final class Player implements } private void saveStreamProgressState(final long progressMillis) { + //noinspection SimplifyOptionalCallChains if (!getCurrentStreamInfo().isPresent() || !prefs.getBoolean(context.getString(R.string.enable_watch_history_key), true)) { return; @@ -3026,17 +1807,10 @@ public final class Player implements Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName()); } + UIs.call(playerUi -> playerUi.onMetadataChanged(info)); + initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); - updateStreamRelatedViews(); - showHideKodiButton(); - - binding.titleTextView.setText(info.getName()); - binding.channelTextView.setText(info.getUploaderName()); - - this.seekbarPreviewThumbnailHolder.resetFrom(this.getContext(), info.getPreviewFrames()); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); final boolean showThumbnail = prefs.getBoolean( context.getString(R.string.show_thumbnail_key), true); @@ -3048,17 +1822,7 @@ public final class Player implements ); notifyMetadataUpdateToListeners(); - - if (areSegmentsVisible) { - if (segmentAdapter.setItems(info)) { - final int adapterPosition = getNearestStreamSegmentPosition( - simpleExoPlayer.getCurrentPosition()); - segmentAdapter.selectSegmentAt(adapterPosition); - binding.itemsList.scrollToPosition(adapterPosition); - } else { - closeItemsList(); - } - } + NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void updateMetadataWith(@NonNull final StreamInfo streamInfo) { @@ -3072,15 +1836,15 @@ public final class Player implements } @NonNull - private String getVideoUrl() { + public String getVideoUrl() { return currentMetadata == null ? context.getString(R.string.unknown_content) : currentMetadata.getStreamUrl(); } @NonNull - private String getVideoUrlAtCurrentTime() { - final int timeSeconds = binding.playbackSeekBar.getProgress() / 1000; + public String getVideoUrlAtCurrentTime() { + final long timeSeconds = simpleExoPlayer.getCurrentPosition() / 1000; String videoUrl = getVideoUrl(); if (!isLive() && timeSeconds >= 0 && currentMetadata != null && currentMetadata.getServiceId() == YouTube.getServiceId()) { @@ -3156,190 +1920,10 @@ public final class Player implements @Override public void onPlayQueueEdited() { notifyPlaybackUpdateToListeners(); - showOrHideButtons(); + UIs.call(PlayerUi::onPlayQueueEdited); NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } - private void onQueueClicked() { - isQueueVisible = true; - - hideSystemUIIfNeeded(); - buildQueue(); - - binding.itemsListHeaderTitle.setVisibility(View.GONE); - binding.itemsListHeaderDuration.setVisibility(View.VISIBLE); - binding.shuffleButton.setVisibility(View.VISIBLE); - binding.repeatButton.setVisibility(View.VISIBLE); - binding.addToPlaylistButton.setVisibility(View.VISIBLE); - - hideControls(0, 0); - binding.itemsListPanel.requestFocus(); - animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA); - - binding.itemsList.scrollToPosition(playQueue.getIndex()); - - updateQueueTime((int) simpleExoPlayer.getCurrentPosition()); - } - - private void buildQueue() { - binding.itemsList.setAdapter(playQueueAdapter); - binding.itemsList.setClickable(true); - binding.itemsList.setLongClickable(true); - - binding.itemsList.clearOnScrollListeners(); - binding.itemsList.addOnScrollListener(getQueueScrollListener()); - - itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); - itemTouchHelper.attachToRecyclerView(binding.itemsList); - - playQueueAdapter.setSelectedListener(getOnSelectedListener()); - - binding.itemsListClose.setOnClickListener(view -> closeItemsList()); - } - - private void onSegmentsClicked() { - areSegmentsVisible = true; - - hideSystemUIIfNeeded(); - buildSegments(); - - binding.itemsListHeaderTitle.setVisibility(View.VISIBLE); - binding.itemsListHeaderDuration.setVisibility(View.GONE); - binding.shuffleButton.setVisibility(View.GONE); - binding.repeatButton.setVisibility(View.GONE); - binding.addToPlaylistButton.setVisibility(View.GONE); - - hideControls(0, 0); - binding.itemsListPanel.requestFocus(); - animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA); - - final int adapterPosition = getNearestStreamSegmentPosition(simpleExoPlayer - .getCurrentPosition()); - segmentAdapter.selectSegmentAt(adapterPosition); - binding.itemsList.scrollToPosition(adapterPosition); - } - - private void buildSegments() { - binding.itemsList.setAdapter(segmentAdapter); - binding.itemsList.setClickable(true); - binding.itemsList.setLongClickable(false); - - binding.itemsList.clearOnScrollListeners(); - if (itemTouchHelper != null) { - itemTouchHelper.attachToRecyclerView(null); - } - - getCurrentStreamInfo().ifPresent(segmentAdapter::setItems); - - binding.shuffleButton.setVisibility(View.GONE); - binding.repeatButton.setVisibility(View.GONE); - binding.addToPlaylistButton.setVisibility(View.GONE); - binding.itemsListClose.setOnClickListener(view -> closeItemsList()); - } - - public void closeItemsList() { - if (isQueueVisible || areSegmentsVisible) { - isQueueVisible = false; - areSegmentsVisible = false; - - if (itemTouchHelper != null) { - itemTouchHelper.attachToRecyclerView(null); - } - - animate(binding.itemsListPanel, false, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA, 0, () -> { - // Even when queueLayout is GONE it receives touch events - // and ruins normal behavior of the app. This line fixes it - binding.itemsListPanel.setTranslationY( - -binding.itemsListPanel.getHeight() * 5); - }); - - // clear focus, otherwise a white rectangle remains on top of the player - binding.itemsListClose.clearFocus(); - binding.playPauseButton.requestFocus(); - } - } - - private OnScrollBelowItemsListener getQueueScrollListener() { - return new OnScrollBelowItemsListener() { - @Override - public void onScrolledDown(final RecyclerView recyclerView) { - if (playQueue != null && !playQueue.isComplete()) { - playQueue.fetch(); - } else if (binding != null) { - binding.itemsList.clearOnScrollListeners(); - } - } - }; - } - - private StreamSegmentAdapter.StreamSegmentListener getStreamSegmentListener() { - return (item, seconds) -> { - segmentAdapter.selectSegment(item); - seekTo(seconds * 1000L); - triggerProgressUpdate(); - }; - } - - private int getNearestStreamSegmentPosition(final long playbackPosition) { - int nearestPosition = 0; - final List segments = getCurrentStreamInfo() - .map(StreamInfo::getStreamSegments) - .orElse(Collections.emptyList()); - - for (int i = 0; i < segments.size(); i++) { - if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) { - break; - } - nearestPosition++; - } - return Math.max(0, nearestPosition - 1); - } - - private ItemTouchHelper.SimpleCallback getItemTouchCallback() { - return new PlayQueueItemTouchCallback() { - @Override - public void onMove(final int sourceIndex, final int targetIndex) { - if (playQueue != null) { - playQueue.move(sourceIndex, targetIndex); - } - } - - @Override - public void onSwiped(final int index) { - if (index != -1) { - playQueue.remove(index); - } - } - }; - } - - private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() { - return new PlayQueueItemBuilder.OnSelectedListener() { - @Override - public void selected(final PlayQueueItem item, final View view) { - selectQueueItem(item); - } - - @Override - public void held(final PlayQueueItem item, final View view) { - if (playQueue.indexOf(item) != -1) { - openPopupMenu(playQueue, item, view, true, - getParentActivity().getSupportFragmentManager(), context); - } - } - - @Override - public void onStartDrag(final PlayQueueItemHolder viewHolder) { - if (itemTouchHelper != null) { - itemTouchHelper.startDrag(viewHolder); - } - } - }; - } - @Override // own playback listener @Nullable public MediaSource sourceOf(final PlayQueueItem item, final StreamInfo info) { @@ -3372,279 +1956,21 @@ public final class Player implements @Nullable public VideoStream getSelectedVideoStream() { - return (selectedStreamIndex >= 0 && availableStreams != null - && availableStreams.size() > selectedStreamIndex) - ? availableStreams.get(selectedStreamIndex) : null; - } - - private void updateStreamRelatedViews() { - if (!getCurrentStreamInfo().isPresent()) { - return; - } - final StreamInfo info = getCurrentStreamInfo().get(); - - binding.qualityTextView.setVisibility(View.GONE); - binding.playbackSpeed.setVisibility(View.GONE); - - binding.playbackEndTime.setVisibility(View.GONE); - binding.playbackLiveSync.setVisibility(View.GONE); - - switch (info.getStreamType()) { - case AUDIO_STREAM: - case POST_LIVE_AUDIO_STREAM: - binding.surfaceView.setVisibility(View.GONE); - binding.endScreen.setVisibility(View.VISIBLE); - binding.playbackEndTime.setVisibility(View.VISIBLE); - break; - - case AUDIO_LIVE_STREAM: - binding.surfaceView.setVisibility(View.GONE); - binding.endScreen.setVisibility(View.VISIBLE); - binding.playbackLiveSync.setVisibility(View.VISIBLE); - break; - - case LIVE_STREAM: - binding.surfaceView.setVisibility(View.VISIBLE); - binding.endScreen.setVisibility(View.GONE); - binding.playbackLiveSync.setVisibility(View.VISIBLE); - break; - - case VIDEO_STREAM: - case POST_LIVE_STREAM: - if (currentMetadata == null - || !currentMetadata.getMaybeQuality().isPresent() - || (info.getVideoStreams().isEmpty() - && info.getVideoOnlyStreams().isEmpty())) { - break; - } - - availableStreams = currentMetadata.getMaybeQuality().get().getSortedVideoStreams(); - selectedStreamIndex = - currentMetadata.getMaybeQuality().get().getSelectedVideoStreamIndex(); - buildQualityMenu(); - - binding.qualityTextView.setVisibility(View.VISIBLE); - binding.surfaceView.setVisibility(View.VISIBLE); - default: - binding.endScreen.setVisibility(View.GONE); - binding.playbackEndTime.setVisibility(View.VISIBLE); - break; + @Nullable final MediaItemTag.Quality quality = Optional.ofNullable(currentMetadata) + .flatMap(MediaItemTag::getMaybeQuality) + .orElse(null); + if (quality == null) { + return null; } - buildPlaybackSpeedMenu(); - binding.playbackSpeed.setVisibility(View.VISIBLE); - } + final List availableStreams = quality.getSortedVideoStreams(); + final int selectedStreamIndex = quality.getSelectedVideoStreamIndex(); - private void updateQueueTime(final int currentTime) { - final int currentStream = playQueue.getIndex(); - int before = 0; - int after = 0; - - final List streams = playQueue.getStreams(); - final int nStreams = streams.size(); - - for (int i = 0; i < nStreams; i++) { - if (i < currentStream) { - before += streams.get(i).getDuration(); - } else { - after += streams.get(i).getDuration(); - } + if (selectedStreamIndex >= 0 && availableStreams.size() > selectedStreamIndex) { + return availableStreams.get(selectedStreamIndex); + } else { + return null; } - - before *= 1000; - after *= 1000; - - binding.itemsListHeaderDuration.setText( - String.format("%s/%s", - getTimeString(currentTime + before), - getTimeString(before + after) - )); - } - //endregion - - - - /*////////////////////////////////////////////////////////////////////////// - // Popup menus ("popup" means that they pop up, not that they belong to the popup player) - //////////////////////////////////////////////////////////////////////////*/ - //region Popup menus ("popup" means that they pop up, not that they belong to the popup player) - - private void buildQualityMenu() { - if (qualityPopupMenu == null) { - return; - } - qualityPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_QUALITY); - - for (int i = 0; i < availableStreams.size(); i++) { - final VideoStream videoStream = availableStreams.get(i); - qualityPopupMenu.getMenu().add(POPUP_MENU_ID_QUALITY, i, Menu.NONE, MediaFormat - .getNameById(videoStream.getFormatId()) + " " + videoStream.getResolution()); - } - if (getSelectedVideoStream() != null) { - binding.qualityTextView.setText(getSelectedVideoStream().getResolution()); - } - qualityPopupMenu.setOnMenuItemClickListener(this); - qualityPopupMenu.setOnDismissListener(this); - } - - private void buildPlaybackSpeedMenu() { - if (playbackSpeedPopupMenu == null) { - return; - } - playbackSpeedPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_PLAYBACK_SPEED); - - for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) { - playbackSpeedPopupMenu.getMenu().add(POPUP_MENU_ID_PLAYBACK_SPEED, i, Menu.NONE, - formatSpeed(PLAYBACK_SPEEDS[i])); - } - binding.playbackSpeed.setText(formatSpeed(getPlaybackSpeed())); - playbackSpeedPopupMenu.setOnMenuItemClickListener(this); - playbackSpeedPopupMenu.setOnDismissListener(this); - } - - private void buildCaptionMenu(@NonNull final List availableLanguages) { - if (captionPopupMenu == null) { - return; - } - captionPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_CAPTION); - captionPopupMenu.setOnDismissListener(this); - - // Add option for turning off caption - final MenuItem captionOffItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, - 0, Menu.NONE, R.string.caption_none); - captionOffItem.setOnMenuItemClickListener(menuItem -> { - final int textRendererIndex = getCaptionRendererIndex(); - if (textRendererIndex != RENDERER_UNAVAILABLE) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setRendererDisabled(textRendererIndex, true)); - } - prefs.edit().remove(context.getString(R.string.caption_user_set_key)).apply(); - return true; - }); - - // Add all available captions - for (int i = 0; i < availableLanguages.size(); i++) { - final String captionLanguage = availableLanguages.get(i); - final MenuItem captionItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, - i + 1, Menu.NONE, captionLanguage); - captionItem.setOnMenuItemClickListener(menuItem -> { - final int textRendererIndex = getCaptionRendererIndex(); - if (textRendererIndex != RENDERER_UNAVAILABLE) { - // DefaultTrackSelector will select for text tracks in the following order. - // When multiple tracks share the same rank, a random track will be chosen. - // 1. ANY track exactly matching preferred language name - // 2. ANY track exactly matching preferred language stem - // 3. ROLE_FLAG_CAPTION track matching preferred language stem - // 4. ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND track matching preferred language stem - // This means if a caption track of preferred language is not available, - // then an auto-generated track of that language will be chosen automatically. - trackSelector.setParameters(trackSelector.buildUponParameters() - .setPreferredTextLanguages(captionLanguage, - PlayerHelper.captionLanguageStemOf(captionLanguage)) - .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) - .setRendererDisabled(textRendererIndex, false)); - prefs.edit().putString(context.getString(R.string.caption_user_set_key), - captionLanguage).apply(); - } - return true; - }); - } - - // apply caption language from previous user preference - final int textRendererIndex = getCaptionRendererIndex(); - if (textRendererIndex == RENDERER_UNAVAILABLE) { - return; - } - - // If user prefers to show no caption, then disable the renderer. - // Otherwise, DefaultTrackSelector may automatically find an available caption - // and display that. - final String userPreferredLanguage = - prefs.getString(context.getString(R.string.caption_user_set_key), null); - if (userPreferredLanguage == null) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setRendererDisabled(textRendererIndex, true)); - return; - } - - // Only set preferred language if it does not match the user preference, - // otherwise there might be an infinite cycle at onTextTracksChanged. - final List selectedPreferredLanguages = - trackSelector.getParameters().preferredTextLanguages; - if (!selectedPreferredLanguages.contains(userPreferredLanguage)) { - trackSelector.setParameters(trackSelector.buildUponParameters() - .setPreferredTextLanguages(userPreferredLanguage, - PlayerHelper.captionLanguageStemOf(userPreferredLanguage)) - .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) - .setRendererDisabled(textRendererIndex, false)); - } - } - - /** - * Called when an item of the quality selector or the playback speed selector is selected. - */ - @Override - public boolean onMenuItemClick(@NonNull final MenuItem menuItem) { - if (DEBUG) { - Log.d(TAG, "onMenuItemClick() called with: " - + "menuItem = [" + menuItem + "], " - + "menuItem.getItemId = [" + menuItem.getItemId() + "]"); - } - - if (menuItem.getGroupId() == POPUP_MENU_ID_QUALITY) { - final int menuItemIndex = menuItem.getItemId(); - if (selectedStreamIndex == menuItemIndex || availableStreams == null - || availableStreams.size() <= menuItemIndex) { - return true; - } - - saveStreamProgressState(); //TODO added, check if good - final String newResolution = availableStreams.get(menuItemIndex).getResolution(); - setRecovery(); - setPlaybackQuality(newResolution); - reloadPlayQueueManager(); - - binding.qualityTextView.setText(menuItem.getTitle()); - return true; - } else if (menuItem.getGroupId() == POPUP_MENU_ID_PLAYBACK_SPEED) { - final int speedIndex = menuItem.getItemId(); - final float speed = PLAYBACK_SPEEDS[speedIndex]; - - setPlaybackSpeed(speed); - binding.playbackSpeed.setText(formatSpeed(speed)); - } - - return false; - } - - /** - * Called when some popup menu is dismissed. - */ - @Override - public void onDismiss(@Nullable final PopupMenu menu) { - if (DEBUG) { - Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); - } - isSomePopupMenuVisible = false; //TODO check if this works - if (getSelectedVideoStream() != null) { - binding.qualityTextView.setText(getSelectedVideoStream().getResolution()); - } - if (isPlaying()) { - hideControls(DEFAULT_CONTROLS_DURATION, 0); - hideSystemUIIfNeeded(); - } - } - - private void onCaptionClicked() { - if (DEBUG) { - Log.d(TAG, "onCaptionClicked() called"); - } - captionPopupMenu.show(); - isSomePopupMenuVisible = true; - } - - private void setPlaybackQuality(@Nullable final String quality) { - videoResolver.setPlaybackQuality(quality); } //endregion @@ -3655,68 +1981,7 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ //region Captions (text tracks) - private void setupSubtitleView() { - final float captionScale = PlayerHelper.getCaptionScale(context); - final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context); - if (popupPlayerSelected()) { - final float captionRatio = (captionScale - 1.0f) / 5.0f + 1.0f; - binding.subtitleView.setFractionalTextSize( - SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio); - } else { - final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); - final float captionRatioInverse = 20f + 4f * (1.0f - captionScale); - binding.subtitleView.setFixedTextSize( - TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse); - } - binding.subtitleView.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT); - binding.subtitleView.setStyle(captionStyle); - } - - private void onTextTracksChanged(@NonNull final Tracks currentTrack) { - if (binding == null) { - return; - } - - final boolean trackTypeTextSupported = !currentTrack.containsType(C.TRACK_TYPE_TEXT) - || currentTrack.isTypeSupported(C.TRACK_TYPE_TEXT, false); - if (trackSelector.getCurrentMappedTrackInfo() == null || !trackTypeTextSupported) { - binding.captionTextView.setVisibility(View.GONE); - return; - } - - // Extract all loaded languages - final List textTracks = currentTrack - .getGroups() - .stream() - .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) - .collect(Collectors.toList()); - final List availableLanguages = textTracks.stream() - .map(Tracks.Group::getMediaTrackGroup) - .filter(textTrack -> textTrack.length > 0) - .map(textTrack -> textTrack.getFormat(0).language) - .collect(Collectors.toList()); - - // Find selected text track - final Optional selectedTracks = textTracks.stream() - .filter(Tracks.Group::isSelected) - .filter(info -> info.getMediaTrackGroup().length >= 1) - .map(info -> info.getMediaTrackGroup().getFormat(0)) - .findFirst(); - - // Build UI - buildCaptionMenu(availableLanguages); - if (trackSelector.getParameters().getRendererDisabled(getCaptionRendererIndex()) - || !selectedTracks.isPresent()) { - binding.captionTextView.setText(R.string.caption_none); - } else { - binding.captionTextView.setText(selectedTracks.get().language); - } - binding.captionTextView.setVisibility( - availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); - } - - private int getCaptionRendererIndex() { + public int getCaptionRendererIndex() { if (exoPlayerIsNull()) { return RENDERER_UNAVAILABLE; } @@ -3732,218 +1997,10 @@ public final class Player implements //endregion - - /*////////////////////////////////////////////////////////////////////////// - // Click listeners - //////////////////////////////////////////////////////////////////////////*/ - //region Click listeners - - @Override - public void onClick(final View v) { - if (DEBUG) { - Log.d(TAG, "onClick() called with: v = [" + v + "]"); - } - if (v.getId() == binding.resizeTextView.getId()) { - onResizeClicked(); - } else if (v.getId() == binding.captionTextView.getId()) { - onCaptionClicked(); - } else if (v.getId() == binding.playbackLiveSync.getId()) { - seekToDefault(); - } else if (v.getId() == binding.playPauseButton.getId()) { - playPause(); - } else if (v.getId() == binding.playPreviousButton.getId()) { - playPrevious(); - } else if (v.getId() == binding.playNextButton.getId()) { - playNext(); - } else if (v.getId() == binding.moreOptionsButton.getId()) { - onMoreOptionsClicked(); - } else if (v.getId() == binding.share.getId()) { - ShareUtils.shareText(context, getVideoTitle(), getVideoUrlAtCurrentTime(), - currentItem.getThumbnailUrl()); - } else if (v.getId() == binding.playWithKodi.getId()) { - onPlayWithKodiClicked(); - } else if (v.getId() == binding.openInBrowser.getId()) { - onOpenInBrowserClicked(); - } else if (v.getId() == binding.fullScreenButton.getId()) { - setRecovery(); - NavigationHelper.playOnMainPlayer(context, playQueue, true); - return; - } else if (v.getId() == binding.screenRotationButton.getId()) { - // Only if it's not a vertical video or vertical video but in landscape with locked - // orientation a screen orientation can be changed automatically - if (!isVerticalVideo - || (service.isLandscape() && globalScreenOrientationLocked(context))) { - fragmentListener.onScreenRotationButtonClicked(); - } else { - toggleFullscreen(); - } - } else if (v.getId() == binding.switchMute.getId()) { - onMuteUnmuteButtonClicked(); - } else if (v.getId() == binding.playerCloseButton.getId()) { - context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)); - } - - manageControlsAfterOnClick(v); - } - - /** - * Manages the controls after a click occurred on the player UI. - * @param v – The view that was clicked - */ - public void manageControlsAfterOnClick(@NonNull final View v) { - if (currentState == STATE_COMPLETED) { - return; - } - - controlsVisibilityHandler.removeCallbacksAndMessages(null); - showHideShadow(true, DEFAULT_CONTROLS_DURATION); - animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, - AnimationType.ALPHA, 0, () -> { - if (currentState == STATE_PLAYING && !isSomePopupMenuVisible) { - if (v.getId() == binding.playPauseButton.getId() - // Hide controls in fullscreen immediately - || (v.getId() == binding.screenRotationButton.getId() - && isFullscreen)) { - hideControls(0, 0); - } else { - hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); - } - } - }); - } - - @Override - public boolean onLongClick(final View v) { - if (v.getId() == binding.moreOptionsButton.getId() && isFullscreen) { - fragmentListener.onMoreOptionsLongClicked(); - hideControls(0, 0); - hideSystemUIIfNeeded(); - } else if (v.getId() == binding.share.getId()) { - ShareUtils.copyToClipboard(context, getVideoUrlAtCurrentTime()); - } - return true; - } - - public boolean onKeyDown(final int keyCode) { - switch (keyCode) { - default: - break; - case KeyEvent.KEYCODE_SPACE: - if (isFullscreen) { - playPause(); - if (isPlaying()) { - hideControls(0, 0); - } - return true; - } - break; - case KeyEvent.KEYCODE_BACK: - if (DeviceUtils.isTv(context) && isControlsVisible()) { - hideControls(0, 0); - return true; - } - break; - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_DPAD_CENTER: - if ((binding.getRoot().hasFocus() && !binding.playbackControlRoot.hasFocus()) - || isQueueVisible) { - // do not interfere with focus in playlist and play queue etc. - return false; - } - - if (currentState == Player.STATE_BLOCKED) { - return true; - } - - if (isControlsVisible()) { - hideControls(DEFAULT_CONTROLS_DURATION, DPAD_CONTROLS_HIDE_TIME); - } else { - binding.playPauseButton.requestFocus(); - showControlsThenHide(); - showSystemUIPartially(); - return true; - } - break; - } - - return false; - } - - private void onMoreOptionsClicked() { - if (DEBUG) { - Log.d(TAG, "onMoreOptionsClicked() called"); - } - - final boolean isMoreControlsVisible = - binding.secondaryControls.getVisibility() == View.VISIBLE; - - animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, - isMoreControlsVisible ? 0 : 180); - animate(binding.secondaryControls, !isMoreControlsVisible, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA, 0, () -> { - // Fix for a ripple effect on background drawable. - // When view returns from GONE state it takes more milliseconds than returning - // from INVISIBLE state. And the delay makes ripple background end to fast - if (isMoreControlsVisible) { - binding.secondaryControls.setVisibility(View.INVISIBLE); - } - }); - showControls(DEFAULT_CONTROLS_DURATION); - } - - private void onPlayWithKodiClicked() { - if (currentMetadata != null) { - pause(); - try { - NavigationHelper.playWithKore(context, Uri.parse(getVideoUrl())); - } catch (final Exception e) { - if (DEBUG) { - Log.i(TAG, "Failed to start kore", e); - } - KoreUtils.showInstallKoreDialog(getParentActivity()); - } - } - } - - private void onOpenInBrowserClicked() { - getCurrentStreamInfo() - .map(Info::getOriginalUrl) - .ifPresent(originalUrl -> ShareUtils.openUrlInBrowser( - Objects.requireNonNull(getParentActivity()), originalUrl)); - } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Video size, resize, orientation, fullscreen //////////////////////////////////////////////////////////////////////////*/ //region Video size, resize, orientation, fullscreen - - private void setupScreenRotationButton() { - binding.screenRotationButton.setVisibility(videoPlayerSelected() - && (globalScreenOrientationLocked(context) || isVerticalVideo - || DeviceUtils.isTablet(context)) - ? View.VISIBLE : View.GONE); - binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(context, - isFullscreen ? R.drawable.ic_fullscreen_exit - : R.drawable.ic_fullscreen)); - } - - private void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { - binding.surfaceView.setResizeMode(resizeMode); - binding.resizeTextView.setText(PlayerHelper.resizeTypeOf(context, resizeMode)); - } - - void onResizeClicked() { - if (binding != null) { - setResizeMode(nextResizeModeAndSaveToPrefs(this, binding.surfaceView.getResizeMode())); - } - } - @Override // exoplayer listener public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { if (DEBUG) { @@ -3954,137 +2011,11 @@ public final class Player implements + "pixelWidthHeightRatio = [" + videoSize.pixelWidthHeightRatio + "]"); } - binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height); - isVerticalVideo = videoSize.width < videoSize.height; - - if (globalScreenOrientationLocked(context) - && isFullscreen - && service.isLandscape() == isVerticalVideo - && !DeviceUtils.isTv(context) - && !DeviceUtils.isTablet(context) - && fragmentListener != null) { - // set correct orientation - fragmentListener.onScreenRotationButtonClicked(); - } - - setupScreenRotationButton(); - } - - public void toggleFullscreen() { - if (DEBUG) { - Log.d(TAG, "toggleFullscreen() called"); - } - if (popupPlayerSelected() || exoPlayerIsNull() || fragmentListener == null) { - return; - } - - isFullscreen = !isFullscreen; - if (!isFullscreen) { - // Apply window insets because Android will not do it when orientation changes - // from landscape to portrait (open vertical video to reproduce) - binding.playbackControlRoot.setPadding(0, 0, 0, 0); - } else { - // Android needs tens milliseconds to send new insets but a user is able to see - // how controls changes it's position from `0` to `nav bar height` padding. - // So just hide the controls to hide this visual inconsistency - hideControls(0, 0); - } - fragmentListener.onFullscreenStateChanged(isFullscreen); - - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - binding.playerCloseButton.setVisibility(View.GONE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - binding.playerCloseButton.setVisibility( - videoPlayerSelected() ? View.VISIBLE : View.GONE); - } - setupScreenRotationButton(); - } - - public void checkLandscape() { - final AppCompatActivity parent = getParentActivity(); - final boolean videoInLandscapeButNotInFullscreen = - service.isLandscape() && !isFullscreen && videoPlayerSelected() && !isAudioOnly; - - final boolean notPaused = currentState != STATE_COMPLETED && currentState != STATE_PAUSED; - if (parent != null - && videoInLandscapeButNotInFullscreen - && notPaused - && !DeviceUtils.isTablet(context)) { - toggleFullscreen(); - } + UIs.call(playerUi -> playerUi.onVideoSizeChanged(videoSize)); } //endregion - - /*////////////////////////////////////////////////////////////////////////// - // Gestures - //////////////////////////////////////////////////////////////////////////*/ - //region Gestures - - @SuppressWarnings("checkstyle:ParameterNumber") - private void onLayoutChange(final View view, final int l, final int t, final int r, final int b, - final int ol, final int ot, final int or, final int ob) { - if (l != ol || t != ot || r != or || b != ob) { - // Use smaller value to be consistent between screen orientations - // (and to make usage easier) - final int width = r - l; - final int height = b - t; - final int min = Math.min(width, height); - maxGestureLength = (int) (min * MAX_GESTURE_LENGTH); - - if (DEBUG) { - Log.d(TAG, "maxGestureLength = " + maxGestureLength); - } - - binding.volumeProgressBar.setMax(maxGestureLength); - binding.brightnessProgressBar.setMax(maxGestureLength); - - setInitialGestureValues(); - binding.itemsListPanel.getLayoutParams().height - = height - binding.itemsListPanel.getTop(); - } - } - - private void setInitialGestureValues() { - if (audioReactor != null) { - final float currentVolumeNormalized = - (float) audioReactor.getVolume() / audioReactor.getMaxVolume(); - binding.volumeProgressBar.setProgress( - (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized)); - } - } - - private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) { - final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft() - + closeOverlayBinding.closeButton.getWidth() / 2; - final int closeOverlayButtonY = closeOverlayBinding.closeButton.getTop() - + closeOverlayBinding.closeButton.getHeight() / 2; - - final float fingerX = popupLayoutParams.x + popupMotionEvent.getX(); - final float fingerY = popupLayoutParams.y + popupMotionEvent.getY(); - - return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) - + Math.pow(closeOverlayButtonY - fingerY, 2)); - } - - private float getClosingRadius() { - final int buttonRadius = closeOverlayBinding.closeButton.getWidth() / 2; - // 20% wider than the button itself - return buttonRadius * 1.2f; - } - - public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent) { - return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius(); - } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Activity / fragment binding //////////////////////////////////////////////////////////////////////////*/ @@ -4092,13 +2023,7 @@ public final class Player implements public void setFragmentListener(final PlayerServiceEventListener listener) { fragmentListener = listener; - fragmentIsVisible = true; - // Apply window insets because Android will not do it when orientation changes - // from landscape to portrait - if (!isFullscreen) { - binding.playbackControlRoot.setPadding(0, 0, 0, 0); - } - binding.itemsListPanel.setPadding(0, 0, 0, 0); + UIs.call(PlayerUi::onFragmentListenerSet); notifyQueueUpdateToListeners(); notifyMetadataUpdateToListeners(); notifyPlaybackUpdateToListeners(); @@ -4136,28 +2061,6 @@ public final class Player implements } } - /** - * This will be called when a user goes to another app/activity, turns off a screen. - * We don't want to interrupt playback and don't want to see notification so - * next lines of code will enable audio-only playback only if needed - */ - private void onFragmentStopped() { - if (videoPlayerSelected() && (isPlaying() || isLoading())) { - switch (getMinimizeOnExitAction(context)) { - case MINIMIZE_ON_EXIT_MODE_BACKGROUND: - useVideoSource(false); - break; - case MINIMIZE_ON_EXIT_MODE_POPUP: - setRecovery(); - NavigationHelper.playOnPopupPlayer(getParentActivity(), playQueue, true); - break; - case MINIMIZE_ON_EXIT_MODE_NONE: default: - pause(); - break; - } - } - } - private void notifyQueueUpdateToListeners() { if (fragmentListener != null && playQueue != null) { fragmentListener.onQueueUpdate(playQueue); @@ -4200,27 +2103,12 @@ public final class Player implements } } - @Nullable - public AppCompatActivity getParentActivity() { - // ! instanceof ViewGroup means that view was added via windowManager for Popup - if (binding == null || !(binding.getRoot().getParent() instanceof ViewGroup)) { - return null; - } - - return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext(); - } - - private void useVideoSource(final boolean videoEnabled) { + public void useVideoSource(final boolean videoEnabled) { if (playQueue == null || isAudioOnly == !videoEnabled || audioPlayerSelected()) { return; } isAudioOnly = !videoEnabled; - // When a user returns from background, controls could be hidden but SystemUI will be shown - // 100%. Hide it. - if (!isAudioOnly && !isControlsVisible()) { - hideSystemUIIfNeeded(); - } // The current metadata may be null sometimes (for e.g. when using an unstable connection // in livestreams) so we will be not able to execute the block below. @@ -4332,7 +2220,7 @@ public final class Player implements //////////////////////////////////////////////////////////////////////////*/ //region Getters - private Optional getCurrentStreamInfo() { + public Optional getCurrentStreamInfo() { return Optional.ofNullable(currentMetadata).flatMap(MediaItemTag::getMaybeStreamInfo); } @@ -4344,6 +2232,10 @@ public final class Player implements return simpleExoPlayer == null; } + public ExoPlayer getExoPlayer() { + return simpleExoPlayer; + } + public boolean isStopped() { return exoPlayerIsNull() || simpleExoPlayer.getPlaybackState() == ExoPlayer.STATE_IDLE; } @@ -4356,7 +2248,7 @@ public final class Player implements return !exoPlayerIsNull() && simpleExoPlayer.getPlayWhenReady(); } - private boolean isLoading() { + public boolean isLoading() { return !exoPlayerIsNull() && simpleExoPlayer.isLoading(); } @@ -4372,6 +2264,10 @@ public final class Player implements } } + public void setPlaybackQuality(@Nullable final String quality) { + videoResolver.setPlaybackQuality(quality); + } + @NonNull public Context getContext() { @@ -4397,7 +2293,7 @@ public final class Player implements } public boolean videoPlayerSelected() { - return playerType == PlayerType.VIDEO; + return playerType == PlayerType.MAIN; } public boolean popupPlayerSelected() { @@ -4414,157 +2310,40 @@ public final class Player implements return audioReactor; } - public GestureDetector getGestureDetector() { - return gestureDetector; + public PlayerService getService() { + return service; } - public boolean isFullscreen() { - return isFullscreen; + public boolean isAudioOnly() { + return isAudioOnly; } - public boolean isVerticalVideo() { - return isVerticalVideo; - } - - public boolean isPopupClosing() { - return isPopupClosing; - } - - - public boolean isSomePopupMenuVisible() { - return isSomePopupMenuVisible; - } - - public void setSomePopupMenuVisible(final boolean somePopupMenuVisible) { - isSomePopupMenuVisible = somePopupMenuVisible; - } - - public ImageButton getPlayPauseButton() { - return binding.playPauseButton; - } - - public View getClosingOverlayView() { - return binding.closingOverlay; - } - - public ProgressBar getVolumeProgressBar() { - return binding.volumeProgressBar; - } - - public ProgressBar getBrightnessProgressBar() { - return binding.brightnessProgressBar; - } - - public int getMaxGestureLength() { - return maxGestureLength; - } - - public ImageView getVolumeImageView() { - return binding.volumeImageView; - } - - public RelativeLayout getVolumeRelativeLayout() { - return binding.volumeRelativeLayout; - } - - public ImageView getBrightnessImageView() { - return binding.brightnessImageView; - } - - public RelativeLayout getBrightnessRelativeLayout() { - return binding.brightnessRelativeLayout; - } - - public FloatingActionButton getCloseOverlayButton() { - return closeOverlayBinding.closeButton; - } - - public View getLoadingPanel() { - return binding.loadingPanel; - } - - public TextView getCurrentDisplaySeek() { - return binding.currentDisplaySeek; - } - - public PlayerFastSeekOverlay getFastSeekOverlay() { - return binding.fastSeekOverlay; + @NonNull + public DefaultTrackSelector getTrackSelector() { + return trackSelector; } @Nullable - public WindowManager.LayoutParams getPopupLayoutParams() { - return popupLayoutParams; + public MediaItemTag getCurrentMetadata() { + return currentMetadata; } @Nullable - public WindowManager getWindowManager() { - return windowManager; + public PlayQueueItem getCurrentItem() { + return currentItem; } - public float getScreenWidth() { - return screenWidth; + public Optional getFragmentListener() { + return Optional.ofNullable(fragmentListener); } - public float getScreenHeight() { - return screenHeight; + /** + * @return the user interfaces connected with the player + */ + public PlayerUiList UIs() { + return UIs; } - public View getRootView() { - return binding.getRoot(); - } - - public ExpandableSurfaceView getSurfaceView() { - return binding.surfaceView; - } - - public PlayQueueAdapter getPlayQueueAdapter() { - return playQueueAdapter; - } - - public PlayerBinding getBinding() { - return binding; - } - - //endregion - - - /*////////////////////////////////////////////////////////////////////////// - // SurfaceHolderCallback helpers - //////////////////////////////////////////////////////////////////////////*/ - //region SurfaceHolderCallback helpers - - private void setupVideoSurface() { - // make sure there is nothing left over from previous calls - cleanupVideoSurface(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 - surfaceHolderCallback = new SurfaceHolderCallback(context, simpleExoPlayer); - binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); - final Surface surface = binding.surfaceView.getHolder().getSurface(); - // ensure player is using an unreleased surface, which the surfaceView might not be - // when starting playback on background or during player switching - if (surface.isValid()) { - // initially set the surface manually otherwise - // onRenderedFirstFrame() will not be called - simpleExoPlayer.setVideoSurface(surface); - } - } else { - simpleExoPlayer.setVideoSurfaceView(binding.surfaceView); - } - } - - private void cleanupVideoSurface() { - // Only for API >= 23 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && surfaceHolderCallback != null) { - if (binding != null) { - binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); - } - surfaceHolderCallback.release(); - surfaceHolderCallback = null; - } - } - //endregion - /** * Get the video renderer index of the current playing stream. * @@ -4592,4 +2371,5 @@ public final class Player implements // No video renderer index with at least one track found: return unavailable index .orElse(RENDERER_UNAVAILABLE); } + //endregion } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java similarity index 63% rename from app/src/main/java/org/schabi/newpipe/player/MainPlayer.java rename to app/src/main/java/org/schabi/newpipe/player/PlayerService.java index a9b9f4c87..cf83dc5c2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -19,44 +19,35 @@ 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.IBinder; import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; - -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import org.schabi.newpipe.App; -import org.schabi.newpipe.databinding.PlayerBinding; -import org.schabi.newpipe.util.DeviceUtils; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.ThemeHelper; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - /** * One service for all players. * * @author mauriciocolli */ -public final class MainPlayer extends Service { - private static final String TAG = "MainPlayer"; +public final class PlayerService extends Service { + private static final String TAG = PlayerService.class.getSimpleName(); private static final boolean DEBUG = Player.DEBUG; private Player player; - private WindowManager windowManager; - private final IBinder mBinder = new MainPlayer.LocalBinder(); + private final IBinder mBinder = new PlayerService.LocalBinder(); public enum PlayerType { - VIDEO, + MAIN, AUDIO, POPUP } @@ -67,7 +58,7 @@ public final class MainPlayer extends Service { static final String ACTION_CLOSE = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; - static final String ACTION_PLAY_PAUSE + public static final String ACTION_PLAY_PAUSE = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; static final String ACTION_REPEAT = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; @@ -94,19 +85,12 @@ public final class MainPlayer extends Service { Log.d(TAG, "onCreate() called"); } assureCorrectAppLanguage(this); - windowManager = ContextCompat.getSystemService(this, WindowManager.class); - ThemeHelper.setTheme(this); - createView(); - } - - private void createView() { - final PlayerBinding binding = PlayerBinding.inflate(LayoutInflater.from(this)); player = new Player(this); - player.setupFromView(binding); - - NotificationUtil.getInstance().createNotificationAndStartForeground(player, this); + /*final MainPlayerUi mainPlayerUi = new MainPlayerUi(player, + PlayerBinding.inflate(LayoutInflater.from(this))); + player.UIs().add(mainPlayerUi);*/ } @Override @@ -121,11 +105,6 @@ public final class MainPlayer extends Service { return START_NOT_STICKY; } - if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) - || intent.getStringExtra(Player.PLAY_QUEUE_KEY) != null) { - NotificationUtil.getInstance().createNotificationAndStartForeground(player, this); - } - player.handleIntent(intent); if (player.getMediaSessionManager() != null) { player.getMediaSessionManager().handleMediaButtonIntent(intent); @@ -144,13 +123,7 @@ public final class MainPlayer extends Service { // Releases wifi & cpu, disables keepScreenOn, etc. // We can't just pause the player here because it will make transition // from one stream to a new stream not smooth - player.smoothStopPlayer(); - player.setRecovery(); - - // Android TV will handle back button in case controls will be visible - // (one more additional unneeded click while the player is hidden) - player.hideControls(0, 0); - player.closeItemsList(); + player.smoothStopForImmediateReusing(); // Notification shows information about old stream but if a user selects // a stream from backStack it's not actual anymore @@ -180,18 +153,7 @@ public final class MainPlayer extends Service { private void cleanup() { if (player != null) { - // Exit from fullscreen when user closes the player via notification - if (player.isFullscreen()) { - player.toggleFullscreen(); - } - removeViewFromParent(); - - player.saveStreamProgressState(); - player.setRecovery(); - player.stopActivityBinding(); - player.removePopupFromView(); player.destroy(); - player = null; } } @@ -212,48 +174,14 @@ public final class MainPlayer extends Service { return mBinder; } - /*////////////////////////////////////////////////////////////////////////// - // Utils - //////////////////////////////////////////////////////////////////////////*/ - - boolean isLandscape() { - // DisplayMetrics from activity context knows about MultiWindow feature - // while DisplayMetrics from app context doesn't - return DeviceUtils.isLandscape(player != null && player.getParentActivity() != null - ? player.getParentActivity() : this); - } - - @Nullable - public View getView() { - if (player == null) { - return null; - } - - return player.getRootView(); - } - - public void removeViewFromParent() { - if (getView() != null && getView().getParent() != null) { - if (player.getParentActivity() != null) { - // This means view was added to fragment - final ViewGroup parent = (ViewGroup) getView().getParent(); - parent.removeView(getView()); - } else { - // This means view was added by windowManager for popup player - windowManager.removeViewImmediate(getView()); - } - } - } - - public class LocalBinder extends Binder { - public MainPlayer getService() { - return MainPlayer.this; + public PlayerService getService() { + return PlayerService.this; } public Player getPlayer() { - return MainPlayer.this.player; + return PlayerService.this.player; } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt deleted file mode 100644 index c89eabb47..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/event/BasePlayerGestureListener.kt +++ /dev/null @@ -1,520 +0,0 @@ -package org.schabi.newpipe.player.event - -import android.content.Context -import android.os.Handler -import android.util.Log -import android.view.GestureDetector -import android.view.MotionEvent -import android.view.View -import android.view.ViewConfiguration -import org.schabi.newpipe.ktx.animate -import org.schabi.newpipe.player.MainPlayer -import org.schabi.newpipe.player.Player -import org.schabi.newpipe.player.helper.PlayerHelper -import org.schabi.newpipe.player.helper.PlayerHelper.savePopupPositionAndSizeToPrefs -import kotlin.math.abs -import kotlin.math.hypot -import kotlin.math.max -import kotlin.math.min - -/** - * Base gesture handling for [Player] - * - * This class contains the logic for the player gestures like View preparations - * and provides some abstract methods to make it easier separating the logic from the UI. - */ -abstract class BasePlayerGestureListener( - @JvmField - protected val player: Player, - @JvmField - protected val service: MainPlayer -) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener { - - // /////////////////////////////////////////////////////////////////// - // Abstract methods for VIDEO and POPUP - // /////////////////////////////////////////////////////////////////// - - abstract fun onDoubleTap(event: MotionEvent, portion: DisplayPortion) - - abstract fun onSingleTap(playerType: MainPlayer.PlayerType) - - abstract fun onScroll( - playerType: MainPlayer.PlayerType, - portion: DisplayPortion, - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ) - - abstract fun onScrollEnd(playerType: MainPlayer.PlayerType, event: MotionEvent) - - // /////////////////////////////////////////////////////////////////// - // Abstract methods for POPUP (exclusive) - // /////////////////////////////////////////////////////////////////// - - abstract fun onPopupResizingStart() - - abstract fun onPopupResizingEnd() - - private var initialPopupX: Int = -1 - private var initialPopupY: Int = -1 - - private var isMovingInMain = false - private var isMovingInPopup = false - private var isResizing = false - - private val tossFlingVelocity = PlayerHelper.getTossFlingVelocity() - - // [popup] initial coordinates and distance between fingers - private var initPointerDistance = -1.0 - private var initFirstPointerX = -1f - private var initFirstPointerY = -1f - private var initSecPointerX = -1f - private var initSecPointerY = -1f - - // /////////////////////////////////////////////////////////////////// - // onTouch implementation - // /////////////////////////////////////////////////////////////////// - - override fun onTouch(v: View, event: MotionEvent): Boolean { - return if (player.popupPlayerSelected()) { - onTouchInPopup(v, event) - } else { - onTouchInMain(v, event) - } - } - - private fun onTouchInMain(v: View, event: MotionEvent): Boolean { - player.gestureDetector.onTouchEvent(event) - if (event.action == MotionEvent.ACTION_UP && isMovingInMain) { - isMovingInMain = false - onScrollEnd(MainPlayer.PlayerType.VIDEO, event) - } - return when (event.action) { - MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { - v.parent.requestDisallowInterceptTouchEvent(player.isFullscreen) - true - } - MotionEvent.ACTION_UP -> { - v.parent.requestDisallowInterceptTouchEvent(false) - false - } - else -> true - } - } - - private fun onTouchInPopup(v: View, event: MotionEvent): Boolean { - player.gestureDetector.onTouchEvent(event) - if (event.pointerCount == 2 && !isMovingInPopup && !isResizing) { - if (DEBUG) { - Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.") - } - onPopupResizingStart() - - // record coordinates of fingers - initFirstPointerX = event.getX(0) - initFirstPointerY = event.getY(0) - initSecPointerX = event.getX(1) - initSecPointerY = event.getY(1) - // record distance between fingers - initPointerDistance = hypot( - initFirstPointerX - initSecPointerX.toDouble(), - initFirstPointerY - initSecPointerY.toDouble() - ) - - isResizing = true - } - if (event.action == MotionEvent.ACTION_MOVE && !isMovingInPopup && isResizing) { - if (DEBUG) { - Log.d( - TAG, - "onTouch() ACTION_MOVE > v = [$v], e1.getRaw =" + - "[${event.rawX}, ${event.rawY}]" - ) - } - return handleMultiDrag(event) - } - if (event.action == MotionEvent.ACTION_UP) { - if (DEBUG) { - Log.d( - TAG, - "onTouch() ACTION_UP > v = [$v], e1.getRaw =" + - " [${event.rawX}, ${event.rawY}]" - ) - } - if (isMovingInPopup) { - isMovingInPopup = false - onScrollEnd(MainPlayer.PlayerType.POPUP, event) - } - if (isResizing) { - isResizing = false - - initPointerDistance = (-1).toDouble() - initFirstPointerX = (-1).toFloat() - initFirstPointerY = (-1).toFloat() - initSecPointerX = (-1).toFloat() - initSecPointerY = (-1).toFloat() - - onPopupResizingEnd() - player.changeState(player.currentState) - } - if (!player.isPopupClosing) { - savePopupPositionAndSizeToPrefs(player) - } - } - - v.performClick() - return true - } - - private fun handleMultiDrag(event: MotionEvent): Boolean { - if (initPointerDistance != -1.0 && event.pointerCount == 2) { - // get the movements of the fingers - val firstPointerMove = hypot( - event.getX(0) - initFirstPointerX.toDouble(), - event.getY(0) - initFirstPointerY.toDouble() - ) - val secPointerMove = hypot( - event.getX(1) - initSecPointerX.toDouble(), - event.getY(1) - initSecPointerY.toDouble() - ) - - // minimum threshold beyond which pinch gesture will work - val minimumMove = ViewConfiguration.get(service).scaledTouchSlop - - if (max(firstPointerMove, secPointerMove) > minimumMove) { - // calculate current distance between the pointers - val currentPointerDistance = hypot( - event.getX(0) - event.getX(1).toDouble(), - event.getY(0) - event.getY(1).toDouble() - ) - - val popupWidth = player.popupLayoutParams!!.width.toDouble() - // change co-ordinates of popup so the center stays at the same position - val newWidth = popupWidth * currentPointerDistance / initPointerDistance - initPointerDistance = currentPointerDistance - player.popupLayoutParams!!.x += ((popupWidth - newWidth) / 2.0).toInt() - - player.checkPopupPositionBounds() - player.updateScreenSize() - player.changePopupSize(min(player.screenWidth.toDouble(), newWidth).toInt()) - return true - } - } - return false - } - - // /////////////////////////////////////////////////////////////////// - // Simple gestures - // /////////////////////////////////////////////////////////////////// - - override fun onDown(e: MotionEvent): Boolean { - if (DEBUG) - Log.d(TAG, "onDown called with e = [$e]") - - if (isDoubleTapping && isDoubleTapEnabled) { - doubleTapControls?.onDoubleTapProgressDown(getDisplayPortion(e)) - return true - } - - return if (player.popupPlayerSelected()) - onDownInPopup(e) - else - true - } - - private fun onDownInPopup(e: MotionEvent): Boolean { - // Fix popup position when the user touch it, it may have the wrong one - // because the soft input is visible (the draggable area is currently resized). - player.updateScreenSize() - player.checkPopupPositionBounds() - player.popupLayoutParams?.let { - initialPopupX = it.x - initialPopupY = it.y - } - return super.onDown(e) - } - - override fun onDoubleTap(e: MotionEvent): Boolean { - if (DEBUG) - Log.d(TAG, "onDoubleTap called with e = [$e]") - - onDoubleTap(e, getDisplayPortion(e)) - return true - } - - override fun onSingleTapConfirmed(e: MotionEvent): Boolean { - if (DEBUG) - Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") - - if (isDoubleTapping) - return true - - if (player.popupPlayerSelected()) { - if (player.exoPlayerIsNull()) - return false - - onSingleTap(MainPlayer.PlayerType.POPUP) - return true - } else { - super.onSingleTapConfirmed(e) - if (player.currentState == Player.STATE_BLOCKED) - return true - - onSingleTap(MainPlayer.PlayerType.VIDEO) - } - return true - } - - override fun onLongPress(e: MotionEvent?) { - if (player.popupPlayerSelected()) { - player.updateScreenSize() - player.checkPopupPositionBounds() - player.changePopupSize(player.screenWidth.toInt()) - } - } - - override fun onScroll( - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - return if (player.popupPlayerSelected()) { - onScrollInPopup(initialEvent, movingEvent, distanceX, distanceY) - } else { - onScrollInMain(initialEvent, movingEvent, distanceX, distanceY) - } - } - - override fun onFling( - e1: MotionEvent?, - e2: MotionEvent?, - velocityX: Float, - velocityY: Float - ): Boolean { - return if (player.popupPlayerSelected()) { - val absVelocityX = abs(velocityX) - val absVelocityY = abs(velocityY) - if (absVelocityX.coerceAtLeast(absVelocityY) > tossFlingVelocity) { - if (absVelocityX > tossFlingVelocity) { - player.popupLayoutParams!!.x = velocityX.toInt() - } - if (absVelocityY > tossFlingVelocity) { - player.popupLayoutParams!!.y = velocityY.toInt() - } - player.checkPopupPositionBounds() - player.windowManager!!.updateViewLayout(player.rootView, player.popupLayoutParams) - return true - } - return false - } else { - true - } - } - - private fun onScrollInMain( - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - - if (!player.isFullscreen) { - return false - } - - val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(service) - val isTouchingNavigationBar: Boolean = - initialEvent.y > (player.rootView.height - getNavigationBarHeight(service)) - if (isTouchingStatusBar || isTouchingNavigationBar) { - return false - } - - val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD - if ( - !isMovingInMain && (insideThreshold || abs(distanceX) > abs(distanceY)) || - player.currentState == Player.STATE_COMPLETED - ) { - return false - } - - isMovingInMain = true - - onScroll( - MainPlayer.PlayerType.VIDEO, - getDisplayHalfPortion(initialEvent), - initialEvent, - movingEvent, - distanceX, - distanceY - ) - - return true - } - - private fun onScrollInPopup( - initialEvent: MotionEvent, - movingEvent: MotionEvent, - distanceX: Float, - distanceY: Float - ): Boolean { - - if (isResizing) { - return super.onScroll(initialEvent, movingEvent, distanceX, distanceY) - } - - if (!isMovingInPopup) { - player.closeOverlayButton.animate(true, 200) - } - - isMovingInPopup = true - - val diffX: Float = (movingEvent.rawX - initialEvent.rawX) - var posX: Float = (initialPopupX + diffX) - val diffY: Float = (movingEvent.rawY - initialEvent.rawY) - var posY: Float = (initialPopupY + diffY) - - if (posX > player.screenWidth - player.popupLayoutParams!!.width) { - posX = (player.screenWidth - player.popupLayoutParams!!.width) - } else if (posX < 0) { - posX = 0f - } - - if (posY > player.screenHeight - player.popupLayoutParams!!.height) { - posY = (player.screenHeight - player.popupLayoutParams!!.height) - } else if (posY < 0) { - posY = 0f - } - - player.popupLayoutParams!!.x = posX.toInt() - player.popupLayoutParams!!.y = posY.toInt() - - onScroll( - MainPlayer.PlayerType.POPUP, - getDisplayHalfPortion(initialEvent), - initialEvent, - movingEvent, - distanceX, - distanceY - ) - - player.windowManager!!.updateViewLayout(player.rootView, player.popupLayoutParams) - return true - } - - // /////////////////////////////////////////////////////////////////// - // Multi double tapping - // /////////////////////////////////////////////////////////////////// - - var doubleTapControls: DoubleTapListener? = null - private set - - private val isDoubleTapEnabled: Boolean - get() = doubleTapDelay > 0 - - var isDoubleTapping = false - private set - - fun doubleTapControls(listener: DoubleTapListener) = apply { - doubleTapControls = listener - } - - private var doubleTapDelay = DOUBLE_TAP_DELAY - private val doubleTapHandler: Handler = Handler() - private val doubleTapRunnable = Runnable { - if (DEBUG) - Log.d(TAG, "doubleTapRunnable called") - - isDoubleTapping = false - doubleTapControls?.onDoubleTapFinished() - } - - fun startMultiDoubleTap(e: MotionEvent) { - if (!isDoubleTapping) { - if (DEBUG) - Log.d(TAG, "startMultiDoubleTap called with e = [$e]") - - keepInDoubleTapMode() - doubleTapControls?.onDoubleTapStarted(getDisplayPortion(e)) - } - } - - fun keepInDoubleTapMode() { - if (DEBUG) - Log.d(TAG, "keepInDoubleTapMode called") - - isDoubleTapping = true - doubleTapHandler.removeCallbacks(doubleTapRunnable) - doubleTapHandler.postDelayed(doubleTapRunnable, doubleTapDelay) - } - - fun endMultiDoubleTap() { - if (DEBUG) - Log.d(TAG, "endMultiDoubleTap called") - - isDoubleTapping = false - doubleTapHandler.removeCallbacks(doubleTapRunnable) - doubleTapControls?.onDoubleTapFinished() - } - - // /////////////////////////////////////////////////////////////////// - // Utils - // /////////////////////////////////////////////////////////////////// - - private fun getDisplayPortion(e: MotionEvent): DisplayPortion { - return if (player.playerType == MainPlayer.PlayerType.POPUP && player.popupLayoutParams != null) { - when { - e.x < player.popupLayoutParams!!.width / 3.0 -> DisplayPortion.LEFT - e.x > player.popupLayoutParams!!.width * 2.0 / 3.0 -> DisplayPortion.RIGHT - else -> DisplayPortion.MIDDLE - } - } else /* MainPlayer.PlayerType.VIDEO */ { - when { - e.x < player.rootView.width / 3.0 -> DisplayPortion.LEFT - e.x > player.rootView.width * 2.0 / 3.0 -> DisplayPortion.RIGHT - else -> DisplayPortion.MIDDLE - } - } - } - - // Currently needed for scrolling since there is no action more the middle portion - private fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion { - return if (player.playerType == MainPlayer.PlayerType.POPUP) { - when { - e.x < player.popupLayoutParams!!.width / 2.0 -> DisplayPortion.LEFT_HALF - else -> DisplayPortion.RIGHT_HALF - } - } else /* MainPlayer.PlayerType.VIDEO */ { - when { - e.x < player.rootView.width / 2.0 -> DisplayPortion.LEFT_HALF - else -> DisplayPortion.RIGHT_HALF - } - } - } - - private fun getNavigationBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("navigation_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } - - private fun getStatusBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("status_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } - - companion object { - private const val TAG = "BasePlayerGestListener" - private val DEBUG = Player.DEBUG - - private const val DOUBLE_TAP_DELAY = 550L - private const val MOVEMENT_THRESHOLD = 40 - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java index b5520e8be..84bd9d277 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerEventListener.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.player.event; - import com.google.android.exoplayer2.PlaybackParameters; import org.schabi.newpipe.extractor.stream.StreamInfo; diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java deleted file mode 100644 index a7fb40c47..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerGestureListener.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.schabi.newpipe.player.event; - -import static org.schabi.newpipe.ktx.AnimationType.ALPHA; -import static org.schabi.newpipe.ktx.AnimationType.SCALE_AND_ALPHA; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_DURATION; -import static org.schabi.newpipe.player.Player.DEFAULT_CONTROLS_HIDE_TIME; -import static org.schabi.newpipe.player.Player.STATE_PLAYING; - -import android.app.Activity; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.widget.ProgressBar; - -import androidx.annotation.NonNull; -import androidx.appcompat.content.res.AppCompatResources; - -import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.R; -import org.schabi.newpipe.player.MainPlayer; -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.helper.PlayerHelper; - -/** - * GestureListener for the player - * - * While {@link BasePlayerGestureListener} contains the logic behind the single gestures - * this class focuses on the visual aspect like hiding and showing the controls or changing - * volume/brightness during scrolling for specific events. - */ -public class PlayerGestureListener - extends BasePlayerGestureListener - implements View.OnTouchListener { - private static final String TAG = PlayerGestureListener.class.getSimpleName(); - private static final boolean DEBUG = MainActivity.DEBUG; - - private final int maxVolume; - - public PlayerGestureListener(final Player player, final MainPlayer service) { - super(player, service); - maxVolume = player.getAudioReactor().getMaxVolume(); - } - - @Override - public void onDoubleTap(@NonNull final MotionEvent event, - @NonNull final DisplayPortion portion) { - if (DEBUG) { - Log.d(TAG, "onDoubleTap called with playerType = [" - + player.getPlayerType() + "], portion = [" + portion + "]"); - } - if (player.isSomePopupMenuVisible()) { - player.hideControls(0, 0); - } - - if (portion == DisplayPortion.LEFT || portion == DisplayPortion.RIGHT) { - startMultiDoubleTap(event); - } else if (portion == DisplayPortion.MIDDLE) { - player.playPause(); - } - } - - @Override - public void onSingleTap(@NonNull final MainPlayer.PlayerType playerType) { - if (DEBUG) { - Log.d(TAG, "onSingleTap called with playerType = [" + player.getPlayerType() + "]"); - } - - if (player.isControlsVisible()) { - player.hideControls(150, 0); - return; - } - // -- Controls are not visible -- - - // When player is completed show controls and don't hide them later - if (player.getCurrentState() == Player.STATE_COMPLETED) { - player.showControls(0); - } else { - player.showControlsThenHide(); - } - } - - @Override - public void onScroll(@NonNull final MainPlayer.PlayerType playerType, - @NonNull final DisplayPortion portion, - @NonNull final MotionEvent initialEvent, - @NonNull final MotionEvent movingEvent, - final float distanceX, final float distanceY) { - if (DEBUG) { - Log.d(TAG, "onScroll called with playerType = [" - + player.getPlayerType() + "], portion = [" + portion + "]"); - } - if (playerType == MainPlayer.PlayerType.VIDEO) { - - // -- Brightness and Volume control -- - final boolean isBrightnessGestureEnabled = - PlayerHelper.isBrightnessGestureEnabled(service); - final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(service); - - if (isBrightnessGestureEnabled && isVolumeGestureEnabled) { - if (portion == DisplayPortion.LEFT_HALF) { - onScrollMainBrightness(distanceX, distanceY); - - } else /* DisplayPortion.RIGHT_HALF */ { - onScrollMainVolume(distanceX, distanceY); - } - } else if (isBrightnessGestureEnabled) { - onScrollMainBrightness(distanceX, distanceY); - } else if (isVolumeGestureEnabled) { - onScrollMainVolume(distanceX, distanceY); - } - - } else /* MainPlayer.PlayerType.POPUP */ { - - // -- Determine if the ClosingOverlayView (red X) has to be shown or hidden -- - final View closingOverlayView = player.getClosingOverlayView(); - final boolean showClosingOverlayView = player.isInsideClosingRadius(movingEvent); - // Check if an view is in expected state and if not animate it into the correct state - final int expectedVisibility = showClosingOverlayView ? View.VISIBLE : View.GONE; - if (closingOverlayView.getVisibility() != expectedVisibility) { - animate(closingOverlayView, showClosingOverlayView, 200); - } - } - } - - private void onScrollMainVolume(final float distanceX, final float distanceY) { - // If we just started sliding, change the progress bar to match the system volume - if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) { - final float volumePercent = player - .getAudioReactor().getVolume() / (float) maxVolume; - player.getVolumeProgressBar().setProgress( - (int) (volumePercent * player.getMaxGestureLength())); - } - - player.getVolumeProgressBar().incrementProgressBy((int) distanceY); - final float currentProgressPercent = (float) player - .getVolumeProgressBar().getProgress() / player.getMaxGestureLength(); - final int currentVolume = (int) (maxVolume * currentProgressPercent); - player.getAudioReactor().setVolume(currentVolume); - - if (DEBUG) { - Log.d(TAG, "onScroll().volumeControl, currentVolume = " + currentVolume); - } - - player.getVolumeImageView().setImageDrawable( - AppCompatResources.getDrawable(service, currentProgressPercent <= 0 - ? R.drawable.ic_volume_off - : currentProgressPercent < 0.25 ? R.drawable.ic_volume_mute - : currentProgressPercent < 0.75 ? R.drawable.ic_volume_down - : R.drawable.ic_volume_up) - ); - - if (player.getVolumeRelativeLayout().getVisibility() != View.VISIBLE) { - animate(player.getVolumeRelativeLayout(), true, 200, SCALE_AND_ALPHA); - } - if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { - player.getBrightnessRelativeLayout().setVisibility(View.GONE); - } - } - - private void onScrollMainBrightness(final float distanceX, final float distanceY) { - final Activity parent = player.getParentActivity(); - if (parent == null) { - return; - } - - final Window window = parent.getWindow(); - final WindowManager.LayoutParams layoutParams = window.getAttributes(); - final ProgressBar bar = player.getBrightnessProgressBar(); - final float oldBrightness = layoutParams.screenBrightness; - bar.setProgress((int) (bar.getMax() * Math.max(0, Math.min(1, oldBrightness)))); - bar.incrementProgressBy((int) distanceY); - - final float currentProgressPercent = (float) bar.getProgress() / bar.getMax(); - layoutParams.screenBrightness = currentProgressPercent; - window.setAttributes(layoutParams); - - // Save current brightness level - PlayerHelper.setScreenBrightness(parent, currentProgressPercent); - - if (DEBUG) { - Log.d(TAG, "onScroll().brightnessControl, " - + "currentBrightness = " + currentProgressPercent); - } - - player.getBrightnessImageView().setImageDrawable( - AppCompatResources.getDrawable(service, - currentProgressPercent < 0.25 - ? R.drawable.ic_brightness_low - : currentProgressPercent < 0.75 - ? R.drawable.ic_brightness_medium - : R.drawable.ic_brightness_high) - ); - - if (player.getBrightnessRelativeLayout().getVisibility() != View.VISIBLE) { - animate(player.getBrightnessRelativeLayout(), true, 200, SCALE_AND_ALPHA); - } - if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { - player.getVolumeRelativeLayout().setVisibility(View.GONE); - } - } - - @Override - public void onScrollEnd(@NonNull final MainPlayer.PlayerType playerType, - @NonNull final MotionEvent event) { - if (DEBUG) { - Log.d(TAG, "onScrollEnd called with playerType = [" - + player.getPlayerType() + "]"); - } - - if (player.isControlsVisible() && player.getCurrentState() == STATE_PLAYING) { - player.hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); - } - - if (playerType == MainPlayer.PlayerType.VIDEO) { - if (player.getVolumeRelativeLayout().getVisibility() == View.VISIBLE) { - animate(player.getVolumeRelativeLayout(), false, 200, SCALE_AND_ALPHA, - 200); - } - if (player.getBrightnessRelativeLayout().getVisibility() == View.VISIBLE) { - animate(player.getBrightnessRelativeLayout(), false, 200, SCALE_AND_ALPHA, - 200); - } - } else /* Popup-Player */ { - if (player.isInsideClosingRadius(event)) { - player.closePopup(); - } else if (!player.isPopupClosing()) { - animate(player.getCloseOverlayButton(), false, 200); - animate(player.getClosingOverlayView(), false, 200); - } - } - } - - @Override - public void onPopupResizingStart() { - if (DEBUG) { - Log.d(TAG, "onPopupResizingStart called"); - } - player.getLoadingPanel().setVisibility(View.GONE); - - player.hideControls(0, 0); - animate(player.getFastSeekOverlay(), false, 0); - animate(player.getCurrentDisplaySeek(), false, 0, ALPHA, 0); - } - - @Override - public void onPopupResizingEnd() { - if (DEBUG) { - Log.d(TAG, "onPopupResizingEnd called"); - } - } -} - - diff --git a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java index 359eab8b2..8c18fd2ad 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/PlayerServiceEventListener.java @@ -3,6 +3,8 @@ package org.schabi.newpipe.player.event; import com.google.android.exoplayer2.PlaybackException; public interface PlayerServiceEventListener extends PlayerEventListener { + void onViewCreated(); + void onFullscreenStateChanged(boolean fullscreen); void onScreenRotationButtonClicked(); 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 f774c90a0..8effe2f0e 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,11 @@ package org.schabi.newpipe.player.event; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; public interface PlayerServiceExtendedEventListener extends PlayerServiceEventListener { void onServiceConnected(Player player, - MainPlayer playerService, + PlayerService playerService, boolean playAfterConnect); void onServiceDisconnected(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt new file mode 100644 index 000000000..bd5d6f1c5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -0,0 +1,182 @@ +package org.schabi.newpipe.player.gesture + +import android.os.Handler +import android.util.Log +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View +import org.schabi.newpipe.databinding.PlayerBinding +import org.schabi.newpipe.player.Player +import org.schabi.newpipe.player.ui.VideoPlayerUi + +/** + * Base gesture handling for [Player] + * + * This class contains the logic for the player gestures like View preparations + * and provides some abstract methods to make it easier separating the logic from the UI. + */ +abstract class BasePlayerGestureListener( + private val playerUi: VideoPlayerUi, +) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener { + + protected val player: Player = playerUi.player + protected val binding: PlayerBinding = playerUi.binding + + override fun onTouch(v: View, event: MotionEvent): Boolean { + playerUi.gestureDetector.onTouchEvent(event) + return false + } + + private fun onDoubleTap( + event: MotionEvent, + portion: DisplayPortion + ) { + if (DEBUG) { + Log.d( + TAG, + "onDoubleTap called with playerType = [" + + player.playerType + "], portion = [" + portion + "]" + ) + } + if (playerUi.isSomePopupMenuVisible) { + playerUi.hideControls(0, 0) + } + if (portion === DisplayPortion.LEFT || portion === DisplayPortion.RIGHT) { + startMultiDoubleTap(event) + } else if (portion === DisplayPortion.MIDDLE) { + player.playPause() + } + } + + protected fun onSingleTap() { + if (playerUi.isControlsVisible) { + playerUi.hideControls(150, 0) + return + } + // -- Controls are not visible -- + + // When player is completed show controls and don't hide them later + if (player.currentState == Player.STATE_COMPLETED) { + playerUi.showControls(0) + } else { + playerUi.showControlsThenHide() + } + } + + open fun onScrollEnd(event: MotionEvent) { + if (DEBUG) { + Log.d( + TAG, + "onScrollEnd called with playerType = [" + + player.playerType + "]" + ) + } + if (playerUi.isControlsVisible && player.currentState == Player.STATE_PLAYING) { + playerUi.hideControls( + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, + VideoPlayerUi.DEFAULT_CONTROLS_HIDE_TIME + ) + } + } + + // /////////////////////////////////////////////////////////////////// + // Simple gestures + // /////////////////////////////////////////////////////////////////// + + override fun onDown(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onDown called with e = [$e]") + + if (isDoubleTapping && isDoubleTapEnabled) { + doubleTapControls?.onDoubleTapProgressDown(getDisplayPortion(e)) + return true + } + + return if (onDownNotDoubleTapping(e)) super.onDown(e) else true + } + + /** + * @return true if `super.onDown(e)` should be called, false otherwise + */ + open fun onDownNotDoubleTapping(e: MotionEvent): Boolean { + return false // do not call super.onDown(e) by default, overridden for popup player + } + + override fun onDoubleTap(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onDoubleTap called with e = [$e]") + + onDoubleTap(e, getDisplayPortion(e)) + return true + } + + // /////////////////////////////////////////////////////////////////// + // Multi double tapping + // /////////////////////////////////////////////////////////////////// + + private var doubleTapControls: DoubleTapListener? = null + + private val isDoubleTapEnabled: Boolean + get() = doubleTapDelay > 0 + + var isDoubleTapping = false + private set + + fun doubleTapControls(listener: DoubleTapListener) = apply { + doubleTapControls = listener + } + + private var doubleTapDelay = DOUBLE_TAP_DELAY + private val doubleTapHandler: Handler = Handler() + private val doubleTapRunnable = Runnable { + if (DEBUG) + Log.d(TAG, "doubleTapRunnable called") + + isDoubleTapping = false + doubleTapControls?.onDoubleTapFinished() + } + + private fun startMultiDoubleTap(e: MotionEvent) { + if (!isDoubleTapping) { + if (DEBUG) + Log.d(TAG, "startMultiDoubleTap called with e = [$e]") + + keepInDoubleTapMode() + doubleTapControls?.onDoubleTapStarted(getDisplayPortion(e)) + } + } + + fun keepInDoubleTapMode() { + if (DEBUG) + Log.d(TAG, "keepInDoubleTapMode called") + + isDoubleTapping = true + doubleTapHandler.removeCallbacks(doubleTapRunnable) + doubleTapHandler.postDelayed(doubleTapRunnable, doubleTapDelay) + } + + fun endMultiDoubleTap() { + if (DEBUG) + Log.d(TAG, "endMultiDoubleTap called") + + isDoubleTapping = false + doubleTapHandler.removeCallbacks(doubleTapRunnable) + doubleTapControls?.onDoubleTapFinished() + } + + // /////////////////////////////////////////////////////////////////// + // Utils + // /////////////////////////////////////////////////////////////////// + + abstract fun getDisplayPortion(e: MotionEvent): DisplayPortion + + // Currently needed for scrolling since there is no action more the middle portion + abstract fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion + + companion object { + private const val TAG = "BasePlayerGestListener" + private val DEBUG = Player.DEBUG + + private const val DOUBLE_TAP_DELAY = 550L + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java similarity index 98% rename from app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java rename to app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java index a5de56e75..240009105 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.event; +package org.schabi.newpipe.player.gesture; import android.content.Context; import android.graphics.Rect; diff --git a/app/src/main/java/org/schabi/newpipe/player/event/DisplayPortion.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt similarity index 65% rename from app/src/main/java/org/schabi/newpipe/player/event/DisplayPortion.kt rename to app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt index f15e42897..684f6d326 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/DisplayPortion.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/DisplayPortion.kt @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.event +package org.schabi.newpipe.player.gesture enum class DisplayPortion { LEFT, MIDDLE, RIGHT, LEFT_HALF, RIGHT_HALF diff --git a/app/src/main/java/org/schabi/newpipe/player/event/DoubleTapListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt similarity index 81% rename from app/src/main/java/org/schabi/newpipe/player/event/DoubleTapListener.kt rename to app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt index 84cfb9b8d..1a0b141e6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/DoubleTapListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.event +package org.schabi.newpipe.player.gesture interface DoubleTapListener { fun onDoubleTapStarted(portion: DisplayPortion) {} diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt new file mode 100644 index 000000000..17205fb9a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -0,0 +1,232 @@ +package org.schabi.newpipe.player.gesture + +import android.app.Activity +import android.content.Context +import android.util.Log +import android.view.MotionEvent +import android.view.View +import android.view.View.OnTouchListener +import android.widget.ProgressBar +import androidx.appcompat.content.res.AppCompatResources +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.R +import org.schabi.newpipe.ktx.AnimationType +import org.schabi.newpipe.ktx.animate +import org.schabi.newpipe.player.Player +import org.schabi.newpipe.player.helper.PlayerHelper +import org.schabi.newpipe.player.ui.MainPlayerUi +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +/** + * GestureListener for the player + * + * While [BasePlayerGestureListener] contains the logic behind the single gestures + * this class focuses on the visual aspect like hiding and showing the controls or changing + * volume/brightness during scrolling for specific events. + */ +class MainPlayerGestureListener( + private val playerUi: MainPlayerUi +) : BasePlayerGestureListener(playerUi), OnTouchListener { + private val maxVolume: Int = player.audioReactor.maxVolume + + private var isMoving = false + + override fun onTouch(v: View, event: MotionEvent): Boolean { + super.onTouch(v, event) + if (event.action == MotionEvent.ACTION_UP && isMoving) { + isMoving = false + onScrollEnd(event) + } + return when (event.action) { + MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { + v.parent.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) + true + } + MotionEvent.ACTION_UP -> { + v.parent.requestDisallowInterceptTouchEvent(false) + false + } + else -> true + } + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") + + if (isDoubleTapping) + return true + super.onSingleTapConfirmed(e) + + if (player.currentState != Player.STATE_BLOCKED) + onSingleTap() + return true + } + + private fun onScrollVolume(distanceY: Float) { + // If we just started sliding, change the progress bar to match the system volume + if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { + val volumePercent: Float = player.audioReactor.volume / maxVolume.toFloat() + binding.volumeProgressBar.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt() + } + + binding.volumeProgressBar.incrementProgressBy(distanceY.toInt()) + val currentProgressPercent: Float = + binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH + val currentVolume = (maxVolume * currentProgressPercent).toInt() + player.audioReactor.volume = currentVolume + if (DEBUG) { + Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume") + } + + binding.volumeImageView.setImageDrawable( + AppCompatResources.getDrawable( + player.context, + when { + currentProgressPercent <= 0 -> R.drawable.ic_volume_off + currentProgressPercent < 0.25 -> R.drawable.ic_volume_mute + currentProgressPercent < 0.75 -> R.drawable.ic_volume_down + else -> R.drawable.ic_volume_up + } + ) + ) + + if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { + binding.volumeRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) + } + if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { + binding.volumeRelativeLayout.visibility = View.GONE + } + } + + private fun onScrollBrightness(distanceY: Float) { + val parent: Activity = playerUi.parentActivity + val window = parent.window + val layoutParams = window.attributes + val bar: ProgressBar = binding.brightnessProgressBar + val oldBrightness = layoutParams.screenBrightness + bar.progress = (bar.max * max(0f, min(1f, oldBrightness))).toInt() + bar.incrementProgressBy(distanceY.toInt()) + val currentProgressPercent = bar.progress.toFloat() / bar.max + layoutParams.screenBrightness = currentProgressPercent + window.attributes = layoutParams + + // Save current brightness level + PlayerHelper.setScreenBrightness(parent, currentProgressPercent) + if (DEBUG) { + Log.d( + TAG, + "onScroll().brightnessControl, " + + "currentBrightness = " + currentProgressPercent + ) + } + binding.brightnessImageView.setImageDrawable( + AppCompatResources.getDrawable( + player.context, + if (currentProgressPercent < 0.25) R.drawable.ic_brightness_low else if (currentProgressPercent < 0.75) R.drawable.ic_brightness_medium else R.drawable.ic_brightness_high + ) + ) + if (binding.brightnessRelativeLayout.visibility != View.VISIBLE) { + binding.brightnessRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) + } + if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { + binding.volumeRelativeLayout.visibility = View.GONE + } + } + + override fun onScrollEnd(event: MotionEvent) { + super.onScrollEnd(event) + if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { + binding.volumeRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) + } + if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { + binding.brightnessRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) + } + } + + override fun onScroll( + initialEvent: MotionEvent, + movingEvent: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + + if (!playerUi.isFullscreen) { + return false + } + + val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(player.context) + val isTouchingNavigationBar: Boolean = + initialEvent.y > (binding.root.height - getNavigationBarHeight(player.context)) + if (isTouchingStatusBar || isTouchingNavigationBar) { + return false + } + + val insideThreshold = abs(movingEvent.y - initialEvent.y) <= MOVEMENT_THRESHOLD + if ( + !isMoving && (insideThreshold || abs(distanceX) > abs(distanceY)) || + player.currentState == Player.STATE_COMPLETED + ) { + return false + } + + isMoving = true + + // -- Brightness and Volume control -- + val isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(player.context) + val isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(player.context) + if (isBrightnessGestureEnabled && isVolumeGestureEnabled) { + if (getDisplayHalfPortion(initialEvent) === DisplayPortion.LEFT_HALF) { + onScrollBrightness(distanceY) + } else /* DisplayPortion.RIGHT_HALF */ { + onScrollVolume(distanceY) + } + } else if (isBrightnessGestureEnabled) { + onScrollBrightness(distanceY) + } else if (isVolumeGestureEnabled) { + onScrollVolume(distanceY) + } + + return true + } + + override fun getDisplayPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < binding.root.width / 3.0 -> DisplayPortion.LEFT + e.x > binding.root.width * 2.0 / 3.0 -> DisplayPortion.RIGHT + else -> DisplayPortion.MIDDLE + } + } + + override fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < binding.root.width / 2.0 -> DisplayPortion.LEFT_HALF + else -> DisplayPortion.RIGHT_HALF + } + } + + companion object { + private val TAG = MainPlayerGestureListener::class.java.simpleName + private val DEBUG = MainActivity.DEBUG + private const val MOVEMENT_THRESHOLD = 40 + const val MAX_GESTURE_LENGTH = 0.75f + + private fun getNavigationBarHeight(context: Context): Int { + val resId = context.resources + .getIdentifier("navigation_bar_height", "dimen", "android") + return if (resId > 0) { + context.resources.getDimensionPixelSize(resId) + } else 0 + } + + private fun getStatusBarHeight(context: Context): Int { + val resId = context.resources + .getIdentifier("status_bar_height", "dimen", "android") + return if (resId > 0) { + context.resources.getDimensionPixelSize(resId) + } else 0 + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt new file mode 100644 index 000000000..b8c1bc54c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -0,0 +1,287 @@ +package org.schabi.newpipe.player.gesture + +import android.util.Log +import android.view.MotionEvent +import android.view.View +import android.view.ViewConfiguration +import org.schabi.newpipe.MainActivity +import org.schabi.newpipe.ktx.AnimationType +import org.schabi.newpipe.ktx.animate +import org.schabi.newpipe.player.helper.PlayerHelper +import org.schabi.newpipe.player.ui.PopupPlayerUi +import kotlin.math.abs +import kotlin.math.hypot +import kotlin.math.max +import kotlin.math.min + +class PopupPlayerGestureListener( + private val playerUi: PopupPlayerUi, +) : BasePlayerGestureListener(playerUi) { + + private var isMoving = false + + private var initialPopupX: Int = -1 + private var initialPopupY: Int = -1 + private var isResizing = false + + // initial coordinates and distance between fingers + private var initPointerDistance = -1.0 + private var initFirstPointerX = -1f + private var initFirstPointerY = -1f + private var initSecPointerX = -1f + private var initSecPointerY = -1f + + override fun onTouch(v: View, event: MotionEvent): Boolean { + super.onTouch(v, event) + if (event.pointerCount == 2 && !isMoving && !isResizing) { + if (DEBUG) { + Log.d(TAG, "onTouch() 2 finger pointer detected, enabling resizing.") + } + onPopupResizingStart() + + // record coordinates of fingers + initFirstPointerX = event.getX(0) + initFirstPointerY = event.getY(0) + initSecPointerX = event.getX(1) + initSecPointerY = event.getY(1) + // record distance between fingers + initPointerDistance = hypot( + initFirstPointerX - initSecPointerX.toDouble(), + initFirstPointerY - initSecPointerY.toDouble() + ) + + isResizing = true + } + if (event.action == MotionEvent.ACTION_MOVE && !isMoving && isResizing) { + if (DEBUG) { + Log.d( + TAG, + "onTouch() ACTION_MOVE > v = [$v], e1.getRaw =" + + "[${event.rawX}, ${event.rawY}]" + ) + } + return handleMultiDrag(event) + } + if (event.action == MotionEvent.ACTION_UP) { + if (DEBUG) { + Log.d( + TAG, + "onTouch() ACTION_UP > v = [$v], e1.getRaw =" + + " [${event.rawX}, ${event.rawY}]" + ) + } + if (isMoving) { + isMoving = false + onScrollEnd(event) + } + if (isResizing) { + isResizing = false + + initPointerDistance = (-1).toDouble() + initFirstPointerX = (-1).toFloat() + initFirstPointerY = (-1).toFloat() + initSecPointerX = (-1).toFloat() + initSecPointerY = (-1).toFloat() + + onPopupResizingEnd() + player.changeState(player.currentState) + } + if (!playerUi.isPopupClosing) { + PlayerHelper.savePopupPositionAndSizeToPrefs(playerUi) + } + } + + v.performClick() + return true + } + + override fun onScrollEnd(event: MotionEvent) { + super.onScrollEnd(event) + if (playerUi.isInsideClosingRadius(event)) { + playerUi.closePopup() + } else if (!playerUi.isPopupClosing) { + playerUi.closeOverlayBinding.closeButton.animate(false, 200) + binding.closingOverlay.animate(false, 200) + } + } + + private fun handleMultiDrag(event: MotionEvent): Boolean { + if (initPointerDistance != -1.0 && event.pointerCount == 2) { + // get the movements of the fingers + val firstPointerMove = hypot( + event.getX(0) - initFirstPointerX.toDouble(), + event.getY(0) - initFirstPointerY.toDouble() + ) + val secPointerMove = hypot( + event.getX(1) - initSecPointerX.toDouble(), + event.getY(1) - initSecPointerY.toDouble() + ) + + // minimum threshold beyond which pinch gesture will work + val minimumMove = ViewConfiguration.get(player.context).scaledTouchSlop + + if (max(firstPointerMove, secPointerMove) > minimumMove) { + // calculate current distance between the pointers + val currentPointerDistance = hypot( + event.getX(0) - event.getX(1).toDouble(), + event.getY(0) - event.getY(1).toDouble() + ) + + val popupWidth = playerUi.popupLayoutParams.width.toDouble() + // change co-ordinates of popup so the center stays at the same position + val newWidth = popupWidth * currentPointerDistance / initPointerDistance + initPointerDistance = currentPointerDistance + playerUi.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt() + + playerUi.checkPopupPositionBounds() + playerUi.updateScreenSize() + playerUi.changePopupSize(min(playerUi.screenWidth.toDouble(), newWidth).toInt()) + return true + } + } + return false + } + + private fun onPopupResizingStart() { + if (DEBUG) { + Log.d(TAG, "onPopupResizingStart called") + } + binding.loadingPanel.visibility = View.GONE + playerUi.hideControls(0, 0) + binding.fastSeekOverlay.animate(false, 0) + binding.currentDisplaySeek.animate(false, 0, AnimationType.ALPHA, 0) + } + + private fun onPopupResizingEnd() { + if (DEBUG) { + Log.d(TAG, "onPopupResizingEnd called") + } + } + + override fun onLongPress(e: MotionEvent?) { + playerUi.updateScreenSize() + playerUi.checkPopupPositionBounds() + playerUi.changePopupSize(playerUi.screenWidth) + } + + override fun onFling( + e1: MotionEvent?, + e2: MotionEvent?, + velocityX: Float, + velocityY: Float + ): Boolean { + return if (player.popupPlayerSelected()) { + val absVelocityX = abs(velocityX) + val absVelocityY = abs(velocityY) + if (absVelocityX.coerceAtLeast(absVelocityY) > TOSS_FLING_VELOCITY) { + if (absVelocityX > TOSS_FLING_VELOCITY) { + playerUi.popupLayoutParams.x = velocityX.toInt() + } + if (absVelocityY > TOSS_FLING_VELOCITY) { + playerUi.popupLayoutParams.y = velocityY.toInt() + } + playerUi.checkPopupPositionBounds() + playerUi.windowManager.updateViewLayout(binding.root, playerUi.popupLayoutParams) + return true + } + return false + } else { + true + } + } + + override fun onDownNotDoubleTapping(e: MotionEvent): Boolean { + // Fix popup position when the user touch it, it may have the wrong one + // because the soft input is visible (the draggable area is currently resized). + playerUi.updateScreenSize() + playerUi.checkPopupPositionBounds() + playerUi.popupLayoutParams.let { + initialPopupX = it.x + initialPopupY = it.y + } + return true // we want `super.onDown(e)` to be called + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + if (DEBUG) + Log.d(TAG, "onSingleTapConfirmed() called with: e = [$e]") + + if (isDoubleTapping) + return true + if (player.exoPlayerIsNull()) + return false + + onSingleTap() + return true + } + + override fun onScroll( + initialEvent: MotionEvent, + movingEvent: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + + if (isResizing) { + return super.onScroll(initialEvent, movingEvent, distanceX, distanceY) + } + + if (!isMoving) { + playerUi.closeOverlayBinding.closeButton.animate(true, 200) + } + + isMoving = true + + val diffX: Float = (movingEvent.rawX - initialEvent.rawX) + var posX: Float = (initialPopupX + diffX) + val diffY: Float = (movingEvent.rawY - initialEvent.rawY) + var posY: Float = (initialPopupY + diffY) + + if (posX > playerUi.screenWidth - playerUi.popupLayoutParams.width) { + posX = (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() + } else if (posX < 0) { + posX = 0f + } + + if (posY > playerUi.screenHeight - playerUi.popupLayoutParams.height) { + posY = (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() + } else if (posY < 0) { + posY = 0f + } + + playerUi.popupLayoutParams.x = posX.toInt() + playerUi.popupLayoutParams.y = posY.toInt() + + // -- Determine if the ClosingOverlayView (red X) has to be shown or hidden -- + val showClosingOverlayView: Boolean = playerUi.isInsideClosingRadius(movingEvent) + // Check if an view is in expected state and if not animate it into the correct state + val expectedVisibility = if (showClosingOverlayView) View.VISIBLE else View.GONE + if (binding.closingOverlay.visibility != expectedVisibility) { + binding.closingOverlay.animate(showClosingOverlayView, 200) + } + + playerUi.windowManager.updateViewLayout(binding.root, playerUi.popupLayoutParams) + return true + } + + override fun getDisplayPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < playerUi.popupLayoutParams.width / 3.0 -> DisplayPortion.LEFT + e.x > playerUi.popupLayoutParams.width * 2.0 / 3.0 -> DisplayPortion.RIGHT + else -> DisplayPortion.MIDDLE + } + } + + override fun getDisplayHalfPortion(e: MotionEvent): DisplayPortion { + return when { + e.x < playerUi.popupLayoutParams.width / 2.0 -> DisplayPortion.LEFT_HALF + else -> DisplayPortion.RIGHT_HALF + } + } + + companion object { + private val TAG = PopupPlayerGestureListener::class.java.simpleName + private val DEBUG = MainActivity.DEBUG + private const val TOSS_FLING_VELOCITY = 2500 + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 19a5a645b..8a5a4f8d2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -26,7 +26,7 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogPlaybackParameterBinding; -import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; @@ -207,7 +207,7 @@ public class PlaybackParameterDialog extends DialogFragment { ? View.VISIBLE : View.GONE); animateRotation(binding.pitchToogleControlModes, - Player.DEFAULT_CONTROLS_DURATION, + VideoPlayerUi.DEFAULT_CONTROLS_DURATION, isCurrentlyVisible ? 180 : 0); }); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index 2131861bf..ec4cf8602 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.player.helper; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.Player.IDLE_WINDOW_FLAGS; import static org.schabi.newpipe.player.Player.PLAYER_TYPE; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER; @@ -11,6 +10,7 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLA import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; +import static org.schabi.newpipe.player.ui.PopupPlayerUi.IDLE_WINDOW_FLAGS; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.SuppressLint; @@ -49,11 +49,12 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.utils.Utils; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.player.ui.PopupPlayerUi; import org.schabi.newpipe.util.ListHelper; import java.lang.annotation.Retention; @@ -339,10 +340,6 @@ public final class PlayerHelper { return true; } - public static int getTossFlingVelocity() { - return 2500; - } - @NonNull public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) { final CaptioningManager captioningManager = ContextCompat.getSystemService(context, @@ -452,10 +449,10 @@ public final class PlayerHelper { // Utils used by player //////////////////////////////////////////////////////////////////////////// - public static MainPlayer.PlayerType retrievePlayerTypeFromIntent(final Intent intent) { + public static PlayerService.PlayerType retrievePlayerTypeFromIntent(final Intent intent) { // If you want to open popup from the app just include Constants.POPUP_ONLY into an extra - return MainPlayer.PlayerType.values()[ - intent.getIntExtra(PLAYER_TYPE, MainPlayer.PlayerType.VIDEO.ordinal())]; + return PlayerService.PlayerType.values()[ + intent.getIntExtra(PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal())]; } public static boolean isPlaybackResumeEnabled(final Player player) { @@ -529,19 +526,20 @@ public final class PlayerHelper { } /** - * @param player {@code screenWidth} and {@code screenHeight} must have been initialized + * @param playerUi {@code screenWidth} and {@code screenHeight} must have been initialized * @return the popup starting layout params */ @SuppressLint("RtlHardcoded") public static WindowManager.LayoutParams retrievePopupLayoutParamsFromPrefs( - final Player player) { - final boolean popupRememberSizeAndPos = player.getPrefs().getBoolean( - player.getContext().getString(R.string.popup_remember_size_pos_key), true); - final float defaultSize = - player.getContext().getResources().getDimension(R.dimen.popup_default_width); + final PopupPlayerUi playerUi) { + final SharedPreferences prefs = playerUi.getPlayer().getPrefs(); + final Context context = playerUi.getPlayer().getContext(); + + final boolean popupRememberSizeAndPos = prefs.getBoolean( + context.getString(R.string.popup_remember_size_pos_key), true); + final float defaultSize = context.getResources().getDimension(R.dimen.popup_default_width); final float popupWidth = popupRememberSizeAndPos - ? player.getPrefs().getFloat(player.getContext().getString( - R.string.popup_saved_width_key), defaultSize) + ? prefs.getFloat(context.getString(R.string.popup_saved_width_key), defaultSize) : defaultSize; final float popupHeight = getMinimumVideoHeight(popupWidth); @@ -553,27 +551,26 @@ public final class PlayerHelper { popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - final int centerX = (int) (player.getScreenWidth() / 2f - popupWidth / 2f); - final int centerY = (int) (player.getScreenHeight() / 2f - popupHeight / 2f); + final int centerX = (int) (playerUi.getScreenWidth() / 2f - popupWidth / 2f); + final int centerY = (int) (playerUi.getScreenHeight() / 2f - popupHeight / 2f); popupLayoutParams.x = popupRememberSizeAndPos - ? player.getPrefs().getInt(player.getContext().getString( - R.string.popup_saved_x_key), centerX) : centerX; + ? prefs.getInt(context.getString(R.string.popup_saved_x_key), centerX) : centerX; popupLayoutParams.y = popupRememberSizeAndPos - ? player.getPrefs().getInt(player.getContext().getString( - R.string.popup_saved_y_key), centerY) : centerY; + ? prefs.getInt(context.getString(R.string.popup_saved_y_key), centerY) : centerY; return popupLayoutParams; } - public static void savePopupPositionAndSizeToPrefs(final Player player) { - if (player.getPopupLayoutParams() != null) { - player.getPrefs().edit() - .putFloat(player.getContext().getString(R.string.popup_saved_width_key), - player.getPopupLayoutParams().width) - .putInt(player.getContext().getString(R.string.popup_saved_x_key), - player.getPopupLayoutParams().x) - .putInt(player.getContext().getString(R.string.popup_saved_y_key), - player.getPopupLayoutParams().y) + public static void savePopupPositionAndSizeToPrefs(final PopupPlayerUi playerUi) { + if (playerUi.getPopupLayoutParams() != null) { + final Context context = playerUi.getPlayer().getContext(); + playerUi.getPlayer().getPrefs().edit() + .putFloat(context.getString(R.string.popup_saved_width_key), + playerUi.getPopupLayoutParams().width) + .putInt(context.getString(R.string.popup_saved_x_key), + playerUi.getPopupLayoutParams().x) + .putInt(context.getString(R.string.popup_saved_y_key), + playerUi.getPopupLayoutParams().y) .apply(); } } 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 4c09ed3c1..cb613f854 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 @@ -16,7 +16,7 @@ import com.google.android.exoplayer2.PlaybackParameters; import org.schabi.newpipe.App; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; @@ -42,17 +42,17 @@ public final class PlayerHolder { private final PlayerServiceConnection serviceConnection = new PlayerServiceConnection(); private boolean bound; - @Nullable private MainPlayer playerService; + @Nullable private PlayerService playerService; @Nullable private Player player; /** - * Returns the current {@link MainPlayer.PlayerType} of the {@link MainPlayer} service, + * Returns the current {@link PlayerService.PlayerType} of the {@link PlayerService} service, * otherwise `null` if no service running. * * @return Current PlayerType */ @Nullable - public MainPlayer.PlayerType getType() { + public PlayerService.PlayerType getType() { if (player == null) { return null; } @@ -122,7 +122,7 @@ 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, MainPlayer.class)); + ContextCompat.startForegroundService(context, new Intent(context, PlayerService.class)); serviceConnection.doPlayAfterConnect(playAfterConnect); bind(context); } @@ -130,7 +130,7 @@ public final class PlayerHolder { public void stopService() { final Context context = getCommonContext(); unbind(context); - context.stopService(new Intent(context, MainPlayer.class)); + context.stopService(new Intent(context, PlayerService.class)); } class PlayerServiceConnection implements ServiceConnection { @@ -156,7 +156,7 @@ public final class PlayerHolder { if (DEBUG) { Log.d(TAG, "Player service is connected"); } - final MainPlayer.LocalBinder localBinder = (MainPlayer.LocalBinder) service; + final PlayerService.LocalBinder localBinder = (PlayerService.LocalBinder) service; playerService = localBinder.getService(); player = localBinder.getPlayer(); @@ -172,7 +172,7 @@ public final class PlayerHolder { Log.d(TAG, "bind() called"); } - final Intent serviceIntent = new Intent(context, MainPlayer.class); + final Intent serviceIntent = new Intent(context, PlayerService.class); bound = context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); if (!bound) { @@ -211,6 +211,13 @@ public final class PlayerHolder { private final PlayerServiceEventListener internalListener = new PlayerServiceEventListener() { + @Override + public void onViewCreated() { + if (listener != null) { + listener.onViewCreated(); + } + } + @Override public void onFullscreenStateChanged(final boolean fullscreen) { if (listener != null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt b/app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt deleted file mode 100644 index 52eff5a1c..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/listeners/view/PlaybackSpeedClickListener.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.schabi.newpipe.player.listeners.view - -import android.util.Log -import android.view.View -import androidx.appcompat.widget.PopupMenu -import org.schabi.newpipe.MainActivity -import org.schabi.newpipe.player.Player -import org.schabi.newpipe.player.helper.PlaybackParameterDialog - -/** - * Click listener for the playbackSpeed textview of the player - */ -class PlaybackSpeedClickListener( - private val player: Player, - private val playbackSpeedPopupMenu: PopupMenu -) : View.OnClickListener { - - companion object { - private const val TAG: String = "PlaybSpeedClickListener" - } - - override fun onClick(v: View) { - if (MainActivity.DEBUG) { - Log.d(TAG, "onPlaybackSpeedClicked() called") - } - - if (player.videoPlayerSelected()) { - PlaybackParameterDialog.newInstance( - player.playbackSpeed.toDouble(), - player.playbackPitch.toDouble(), - player.playbackSkipSilence - ) { speed: Float, pitch: Float, skipSilence: Boolean -> - player.setPlaybackParameters( - speed, - pitch, - skipSilence - ) - } - .show(player.parentActivity!!.supportFragmentManager, null) - } else { - playbackSpeedPopupMenu.show() - player.isSomePopupMenuVisible = true - } - - player.manageControlsAfterOnClick(v) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt b/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt deleted file mode 100644 index 43e8288e6..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/listeners/view/QualityClickListener.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.schabi.newpipe.player.listeners.view - -import android.annotation.SuppressLint -import android.util.Log -import android.view.View -import androidx.appcompat.widget.PopupMenu -import org.schabi.newpipe.MainActivity -import org.schabi.newpipe.extractor.MediaFormat -import org.schabi.newpipe.player.Player - -/** - * Click listener for the qualityTextView of the player - */ -class QualityClickListener( - private val player: Player, - private val qualityPopupMenu: PopupMenu -) : View.OnClickListener { - - companion object { - private const val TAG: String = "QualityClickListener" - } - - @SuppressLint("SetTextI18n") // we don't need I18N because of a " " - override fun onClick(v: View) { - if (MainActivity.DEBUG) { - Log.d(TAG, "onQualitySelectorClicked() called") - } - - qualityPopupMenu.show() - player.isSomePopupMenuVisible = true - - val videoStream = player.selectedVideoStream - if (videoStream != null) { - player.binding.qualityTextView.text = - MediaFormat.getNameById(videoStream.formatId) + " " + videoStream.getResolution() - } - - player.saveWasPlaying() - player.manageControlsAfterOnClick(v) - } -} diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java index ee0a6f118..2f261a0fa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java @@ -8,6 +8,9 @@ import android.support.v4.media.MediaMetadataCompat; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.mediasession.MediaSessionCallback; import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.player.ui.VideoPlayerUi; + +import java.util.Optional; public class PlayerMediaSession implements MediaSessionCallback { private final Player player; @@ -89,7 +92,7 @@ public class PlayerMediaSession implements MediaSessionCallback { public void play() { player.play(); // hide the player controls even if the play command came from the media session - player.hideControls(0, 0); + player.UIs().get(VideoPlayerUi.class).ifPresent(playerUi -> playerUi.hideControls(0, 0)); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java new file mode 100644 index 000000000..10ed424ba --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -0,0 +1,937 @@ +package org.schabi.newpipe.player.ui; + +import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.player.Player.STATE_COMPLETED; +import static org.schabi.newpipe.player.Player.STATE_PAUSED; +import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; +import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; +import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; +import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; + +import android.content.Intent; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Build; +import android.os.Handler; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.exoplayer2.video.VideoSize; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.PlayerBinding; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.StreamSegment; +import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; +import org.schabi.newpipe.fragments.detail.VideoDetailFragment; +import org.schabi.newpipe.info_list.StreamSegmentAdapter; +import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.event.PlayerServiceEventListener; +import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; +import org.schabi.newpipe.player.gesture.MainPlayerGestureListener; +import org.schabi.newpipe.player.helper.PlaybackParameterDialog; +import org.schabi.newpipe.player.helper.PlayerHelper; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.player.playqueue.PlayQueueItemBuilder; +import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder; +import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback; +import org.schabi.newpipe.util.DeviceUtils; +import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.external_communication.KoreUtils; + +import java.util.List; +import java.util.Objects; + +public final class MainPlayerUi extends VideoPlayerUi { + private static final String TAG = MainPlayerUi.class.getSimpleName(); + + private boolean isFullscreen = false; + private boolean isVerticalVideo = false; + private boolean fragmentIsVisible = false; + + private ContentObserver settingsContentObserver; + + private PlayQueueAdapter playQueueAdapter; + private StreamSegmentAdapter segmentAdapter; + private boolean isQueueVisible = false; + private boolean areSegmentsVisible = false; + + // fullscreen player + private ItemTouchHelper itemTouchHelper; + + public MainPlayerUi(@NonNull final Player player, + @NonNull final PlayerBinding playerBinding) { + super(player, playerBinding); + } + + /** + * Open fullscreen on tablets where the option to have the main player start automatically in + * fullscreen mode is on. Rotating the device to landscape is already done in {@link + * VideoDetailFragment#openVideoPlayer(boolean)} when the thumbnail is clicked, and that's + * enough for phones, but not for tablets since the mini player can be also shown in landscape. + */ + private void directlyOpenFullscreenIfNeeded() { + if (PlayerHelper.isStartMainPlayerFullscreenEnabled(player.getService()) + && DeviceUtils.isTablet(player.getService()) + && PlayerHelper.globalScreenOrientationLocked(player.getService())) { + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onScreenRotationButtonClicked); + } + } + + @Override + public void setupAfterIntent() { + // needed for tablets, check the function for a better explanation + directlyOpenFullscreenIfNeeded(); + + super.setupAfterIntent(); + + binding.getRoot().setVisibility(View.VISIBLE); + initVideoPlayer(); + // Android TV: without it focus will frame the whole player + binding.playPauseButton.requestFocus(); + + // Note: This is for automatically playing (when "Resume playback" is off), see #6179 + if (player.getPlayWhenReady()) { + player.play(); + } else { + player.pause(); + } + } + + @Override + BasePlayerGestureListener buildGestureListener() { + return new MainPlayerGestureListener(this); + } + + @Override + protected void initListeners() { + super.initListeners(); + + binding.queueButton.setOnClickListener(v -> onQueueClicked()); + binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked()); + + binding.addToPlaylistButton.setOnClickListener(v -> + player.onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager())); + + settingsContentObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(final boolean selfChange) { + setupScreenRotationButton(); + } + }; + context.getContentResolver().registerContentObserver( + Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, + settingsContentObserver); + + binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); + } + + @Override + public void initPlayback() { + super.initPlayback(); + + if (playQueueAdapter != null) { + playQueueAdapter.dispose(); + } + playQueueAdapter = new PlayQueueAdapter(context, + Objects.requireNonNull(player.getPlayQueue())); + segmentAdapter = new StreamSegmentAdapter(getStreamSegmentListener()); + } + + @Override + public void removeViewFromParent() { + // view was added to fragment + final ViewParent parent = binding.getRoot().getParent(); + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).removeView(binding.getRoot()); + } + } + + @Override + public void destroy() { + super.destroy(); + context.getContentResolver().unregisterContentObserver(settingsContentObserver); + + // Exit from fullscreen when user closes the player via notification + if (isFullscreen) { + toggleFullscreen(); + } + + removeViewFromParent(); + } + + @Override + public void destroyPlayer() { + super.destroyPlayer(); + + if (playQueueAdapter != null) { + playQueueAdapter.unsetSelectedListener(); + playQueueAdapter.dispose(); + } + } + + @Override + public void smoothStopForImmediateReusing() { + super.smoothStopForImmediateReusing(); + // Android TV will handle back button in case controls will be visible + // (one more additional unneeded click while the player is hidden) + hideControls(0, 0); + closeItemsList(); + } + + private void initVideoPlayer() { + // restore last resize mode + setResizeMode(PlayerHelper.retrieveResizeModeFromPrefs(player)); + binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + } + + @Override + protected void setupElementsVisibility() { + super.setupElementsVisibility(); + + closeItemsList(); + showHideKodiButton(); + binding.fullScreenButton.setVisibility(View.GONE); + setupScreenRotationButton(); + binding.resizeTextView.setVisibility(View.VISIBLE); + binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); + binding.moreOptionsButton.setVisibility(View.VISIBLE); + binding.topControls.setOrientation(LinearLayout.VERTICAL); + binding.primaryControls.getLayoutParams().width + = LinearLayout.LayoutParams.MATCH_PARENT; + binding.secondaryControls.setVisibility(View.INVISIBLE); + binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context, + R.drawable.ic_expand_more)); + binding.share.setVisibility(View.VISIBLE); + binding.openInBrowser.setVisibility(View.VISIBLE); + binding.switchMute.setVisibility(View.VISIBLE); + binding.playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE); + // Top controls have a large minHeight which is allows to drag the player + // down in fullscreen mode (just larger area to make easy to locate by finger) + binding.topControls.setClickable(true); + binding.topControls.setFocusable(true); + + if (isFullscreen) { + binding.titleTextView.setVisibility(View.VISIBLE); + binding.channelTextView.setVisibility(View.VISIBLE); + } else { + binding.titleTextView.setVisibility(View.GONE); + binding.channelTextView.setVisibility(View.GONE); + } + } + + @Override + protected void setupElementsSize(final Resources resources) { + setupElementsSize( + resources.getDimensionPixelSize(R.dimen.player_main_buttons_min_width), + resources.getDimensionPixelSize(R.dimen.player_main_top_padding), + resources.getDimensionPixelSize(R.dimen.player_main_controls_padding), + resources.getDimensionPixelSize(R.dimen.player_main_buttons_padding) + ); + } + + + /*////////////////////////////////////////////////////////////////////////// + // Broadcast receiver + //////////////////////////////////////////////////////////////////////////*/ + //region Broadcast receiver + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + // Close it because when changing orientation from portrait + // (in fullscreen mode) the size of queue layout can be larger than the screen size + closeItemsList(); + } else if (ACTION_PLAY_PAUSE.equals(intent.getAction())) { + // Ensure that we have audio-only stream playing when a user + // started to play from notification's play button from outside of the app + if (!fragmentIsVisible) { + onFragmentStopped(); + } + } else if (VideoDetailFragment.ACTION_VIDEO_FRAGMENT_STOPPED.equals(intent.getAction())) { + fragmentIsVisible = false; + onFragmentStopped(); + } else if (VideoDetailFragment.ACTION_VIDEO_FRAGMENT_RESUMED.equals(intent.getAction())) { + // Restore video source when user returns to the fragment + fragmentIsVisible = true; + player.useVideoSource(true); + + // When a user returns from background, the system UI will always be shown even if + // controls are invisible: hide it in that case + if (!isControlsVisible()) { + hideSystemUIIfNeeded(); + } + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Fragment binding + //////////////////////////////////////////////////////////////////////////*/ + //region Fragment binding + @Override + public void onFragmentListenerSet() { + super.onFragmentListenerSet(); + fragmentIsVisible = true; + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait + if (!isFullscreen) { + binding.playbackControlRoot.setPadding(0, 0, 0, 0); + } + binding.itemsListPanel.setPadding(0, 0, 0, 0); + player.getFragmentListener().ifPresent(PlayerServiceEventListener::onViewCreated); + } + + /** + * This will be called when a user goes to another app/activity, turns off a screen. + * We don't want to interrupt playback and don't want to see notification so + * next lines of code will enable audio-only playback only if needed + */ + private void onFragmentStopped() { + if (player.isPlaying() || player.isLoading()) { + switch (getMinimizeOnExitAction(context)) { + case MINIMIZE_ON_EXIT_MODE_BACKGROUND: + player.useVideoSource(false); + break; + case MINIMIZE_ON_EXIT_MODE_POPUP: + player.setRecovery(); + NavigationHelper.playOnPopupPlayer(getParentActivity(), + player.getPlayQueue(), true); + break; + case MINIMIZE_ON_EXIT_MODE_NONE: default: + player.pause(); + break; + } + } + } + //endregion + + private void showHideKodiButton() { + // show kodi button if it supports the current service and it is enabled in settings + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + binding.playWithKodi.setVisibility(playQueue != null && playQueue.getItem() != null + && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) + ? View.VISIBLE : View.GONE); + } + + @Override + public void onUpdateProgress(final int currentProgress, + final int duration, + final int bufferPercent) { + super.onUpdateProgress(currentProgress, duration, bufferPercent); + + if (areSegmentsVisible) { + segmentAdapter.selectSegmentAt(getNearestStreamSegmentPosition(currentProgress)); + } + if (isQueueVisible) { + updateQueueTime(currentProgress); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Controls showing / hiding + //////////////////////////////////////////////////////////////////////////*/ + //region Controls showing / hiding + + protected void showOrHideButtons() { + super.showOrHideButtons(); + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + final boolean showQueue = playQueue.getStreams().size() > 1; + final boolean showSegment = !player.getCurrentStreamInfo() + .map(StreamInfo::getStreamSegments) + .map(List::isEmpty) + .orElse(/*no stream info=*/true); + + binding.queueButton.setVisibility(showQueue ? View.VISIBLE : View.GONE); + binding.queueButton.setAlpha(showQueue ? 1.0f : 0.0f); + binding.segmentsButton.setVisibility(showSegment ? View.VISIBLE : View.GONE); + binding.segmentsButton.setAlpha(showSegment ? 1.0f : 0.0f); + } + + @Override + public void showSystemUIPartially() { + if (isFullscreen) { + final Window window = getParentActivity().getWindow(); + window.setStatusBarColor(Color.TRANSPARENT); + window.setNavigationBarColor(Color.TRANSPARENT); + final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + window.getDecorView().setSystemUiVisibility(visibility); + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + + @Override + public void hideSystemUIIfNeeded() { + player.getFragmentListener().ifPresent(PlayerServiceEventListener::hideSystemUiIfNeeded); + } + + /** + * Calculate the maximum allowed height for the {@link R.id.endScreen} + * to prevent it from enlarging the player. + *

+ * The calculating follows these rules: + *

    + *
  • + * Show at least stream title and content creator on TVs and tablets + * when in landscape (always the case for TVs) and not in fullscreen mode. + * This requires to have at least 85dp free space for {@link R.id.detail_root} + * and additional space for the stream title text size + * ({@link R.id.detail_title_root_layout}). + * The text size is 15sp on tablets and 16sp on TVs, + * see {@link R.id.titleTextView}. + *
  • + *
  • + * Otherwise, the max thumbnail height is the screen height. + * TODO investigate why this is done on popup player, too + *
  • + *
+ * + * @param bitmap the bitmap that needs to be resized to fit the end screen + * @return the maximum height for the end screen thumbnail + */ + @Override + protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { + final int screenHeight = context.getResources().getDisplayMetrics().heightPixels; + + if (DeviceUtils.isTv(context) && !isFullscreen()) { + final int videoInfoHeight = + DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context); + return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); + } else if (DeviceUtils.isTablet(context) && isLandscape() && !isFullscreen()) { + final int videoInfoHeight = + DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context); + return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); + } else { // fullscreen player: max height is the device height + return Math.min(bitmap.getHeight(), screenHeight); + } + } + //endregion + + @Override + public void onPlaying() { + super.onPlaying(); + checkLandscape(); + } + + @Override + public void onCompleted() { + super.onCompleted(); + if (isFullscreen) { + toggleFullscreen(); + } + } + + + @Override + protected void setupSubtitleView(float captionScale) { + final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); + final float captionRatioInverse = 20f + 4f * (1.0f - captionScale); + binding.subtitleView.setFixedTextSize( + TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse); + } + + + + + /*////////////////////////////////////////////////////////////////////////// + // Gestures + //////////////////////////////////////////////////////////////////////////*/ + //region Gestures + + @SuppressWarnings("checkstyle:ParameterNumber") + private void onLayoutChange(final View view, final int l, final int t, final int r, final int b, + final int ol, final int ot, final int or, final int ob) { + if (l != ol || t != ot || r != or || b != ob) { + // Use smaller value to be consistent between screen orientations + // (and to make usage easier) + final int width = r - l; + final int height = b - t; + final int min = Math.min(width, height); + final int maxGestureLength = (int) (min * MainPlayerGestureListener.MAX_GESTURE_LENGTH); + + if (DEBUG) { + Log.d(TAG, "maxGestureLength = " + maxGestureLength); + } + + binding.volumeProgressBar.setMax(maxGestureLength); + binding.brightnessProgressBar.setMax(maxGestureLength); + + setInitialGestureValues(); + binding.itemsListPanel.getLayoutParams().height + = height - binding.itemsListPanel.getTop(); + } + } + + private void setInitialGestureValues() { + if (player.getAudioReactor() != null) { + final float currentVolumeNormalized = + (float) player.getAudioReactor().getVolume() + / player.getAudioReactor().getMaxVolume(); + binding.volumeProgressBar.setProgress( + (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized)); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Play queue, segments and streams + //////////////////////////////////////////////////////////////////////////*/ + //region Play queue, segments and streams + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + showHideKodiButton(); + if (areSegmentsVisible) { + if (segmentAdapter.setItems(info)) { + final int adapterPosition = getNearestStreamSegmentPosition( + player.getExoPlayer().getCurrentPosition()); + segmentAdapter.selectSegmentAt(adapterPosition); + binding.itemsList.scrollToPosition(adapterPosition); + } else { + closeItemsList(); + } + } + } + + @Override + public void onPlayQueueEdited() { + super.onPlayQueueEdited(); + showOrHideButtons(); + } + + private void onQueueClicked() { + isQueueVisible = true; + + hideSystemUIIfNeeded(); + buildQueue(); + + binding.itemsListHeaderTitle.setVisibility(View.GONE); + binding.itemsListHeaderDuration.setVisibility(View.VISIBLE); + binding.shuffleButton.setVisibility(View.VISIBLE); + binding.repeatButton.setVisibility(View.VISIBLE); + binding.addToPlaylistButton.setVisibility(View.VISIBLE); + + hideControls(0, 0); + binding.itemsListPanel.requestFocus(); + animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA); + + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null) { + binding.itemsList.scrollToPosition(playQueue.getIndex()); + } + + updateQueueTime((int) player.getExoPlayer().getCurrentPosition()); + } + + private void buildQueue() { + binding.itemsList.setAdapter(playQueueAdapter); + binding.itemsList.setClickable(true); + binding.itemsList.setLongClickable(true); + + binding.itemsList.clearOnScrollListeners(); + binding.itemsList.addOnScrollListener(getQueueScrollListener()); + + itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); + itemTouchHelper.attachToRecyclerView(binding.itemsList); + + playQueueAdapter.setSelectedListener(getOnSelectedListener()); + + binding.itemsListClose.setOnClickListener(view -> closeItemsList()); + } + + private void onSegmentsClicked() { + areSegmentsVisible = true; + + hideSystemUIIfNeeded(); + buildSegments(); + + binding.itemsListHeaderTitle.setVisibility(View.VISIBLE); + binding.itemsListHeaderDuration.setVisibility(View.GONE); + binding.shuffleButton.setVisibility(View.GONE); + binding.repeatButton.setVisibility(View.GONE); + binding.addToPlaylistButton.setVisibility(View.GONE); + + hideControls(0, 0); + binding.itemsListPanel.requestFocus(); + animate(binding.itemsListPanel, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA); + + final int adapterPosition = getNearestStreamSegmentPosition( + player.getExoPlayer().getCurrentPosition()); + segmentAdapter.selectSegmentAt(adapterPosition); + binding.itemsList.scrollToPosition(adapterPosition); + } + + private void buildSegments() { + binding.itemsList.setAdapter(segmentAdapter); + binding.itemsList.setClickable(true); + binding.itemsList.setLongClickable(false); + + binding.itemsList.clearOnScrollListeners(); + if (itemTouchHelper != null) { + itemTouchHelper.attachToRecyclerView(null); + } + + player.getCurrentStreamInfo().ifPresent(segmentAdapter::setItems); + + binding.shuffleButton.setVisibility(View.GONE); + binding.repeatButton.setVisibility(View.GONE); + binding.addToPlaylistButton.setVisibility(View.GONE); + binding.itemsListClose.setOnClickListener(view -> closeItemsList()); + } + + public void closeItemsList() { + if (isQueueVisible || areSegmentsVisible) { + isQueueVisible = false; + areSegmentsVisible = false; + + if (itemTouchHelper != null) { + itemTouchHelper.attachToRecyclerView(null); + } + + animate(binding.itemsListPanel, false, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA, 0, () -> { + // Even when queueLayout is GONE it receives touch events + // and ruins normal behavior of the app. This line fixes it + binding.itemsListPanel.setTranslationY( + -binding.itemsListPanel.getHeight() * 5); + }); + + // clear focus, otherwise a white rectangle remains on top of the player + binding.itemsListClose.clearFocus(); + binding.playPauseButton.requestFocus(); + } + } + + private OnScrollBelowItemsListener getQueueScrollListener() { + return new OnScrollBelowItemsListener() { + @Override + public void onScrolledDown(final RecyclerView recyclerView) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null && !playQueue.isComplete()) { + playQueue.fetch(); + } else if (binding != null) { + binding.itemsList.clearOnScrollListeners(); + } + } + }; + } + + private StreamSegmentAdapter.StreamSegmentListener getStreamSegmentListener() { + return (item, seconds) -> { + segmentAdapter.selectSegment(item); + player.seekTo(seconds * 1000L); + player.triggerProgressUpdate(); + }; + } + + private int getNearestStreamSegmentPosition(final long playbackPosition) { + //noinspection SimplifyOptionalCallChains + if (!player.getCurrentStreamInfo().isPresent()) { + return 0; + } + + int nearestPosition = 0; + final List segments + = player.getCurrentStreamInfo().get().getStreamSegments(); + + for (int i = 0; i < segments.size(); i++) { + if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) { + break; + } + nearestPosition++; + } + return Math.max(0, nearestPosition - 1); + } + + private ItemTouchHelper.SimpleCallback getItemTouchCallback() { + return new PlayQueueItemTouchCallback() { + @Override + public void onMove(final int sourceIndex, final int targetIndex) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null) { + playQueue.move(sourceIndex, targetIndex); + } + } + + @Override + public void onSwiped(final int index) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue != null && index != -1) { + playQueue.remove(index); + } + } + }; + } + + private PlayQueueItemBuilder.OnSelectedListener getOnSelectedListener() { + return new PlayQueueItemBuilder.OnSelectedListener() { + @Override + public void selected(final PlayQueueItem item, final View view) { + player.selectQueueItem(item); + } + + @Override + public void held(final PlayQueueItem item, final View view) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + @Nullable final AppCompatActivity parentActivity = getParentActivity(); + if (playQueue != null && parentActivity != null && playQueue.indexOf(item) != -1) { + openPopupMenu(player.getPlayQueue(), item, view, true, + parentActivity.getSupportFragmentManager(), context); + } + } + + @Override + public void onStartDrag(final PlayQueueItemHolder viewHolder) { + if (itemTouchHelper != null) { + itemTouchHelper.startDrag(viewHolder); + } + } + }; + } + + private void updateQueueTime(final int currentTime) { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + final int currentStream = playQueue.getIndex(); + int before = 0; + int after = 0; + + final List streams = playQueue.getStreams(); + final int nStreams = streams.size(); + + for (int i = 0; i < nStreams; i++) { + if (i < currentStream) { + before += streams.get(i).getDuration(); + } else { + after += streams.get(i).getDuration(); + } + } + + before *= 1000; + after *= 1000; + + binding.itemsListHeaderDuration.setText( + String.format("%s/%s", + getTimeString(currentTime + before), + getTimeString(before + after) + )); + } + + @Override + protected boolean isAnyListViewOpen() { + return isQueueVisible || areSegmentsVisible; + } + + @Override + public boolean isFullscreen() { + return isFullscreen; + } + + public boolean isVerticalVideo() { + return isVerticalVideo; + } + + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Click listeners + //////////////////////////////////////////////////////////////////////////*/ + //region Click listeners + @Override + public void onClick(final View v) { + if (v.getId() == binding.screenRotationButton.getId()) { + // Only if it's not a vertical video or vertical video but in landscape with locked + // orientation a screen orientation can be changed automatically + if (!isVerticalVideo || (isLandscape() && globalScreenOrientationLocked(context))) { + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onScreenRotationButtonClicked); + } else { + toggleFullscreen(); + } + } + + // call it later since it calls manageControlsAfterOnClick at the end + super.onClick(v); + } + + @Override + protected void onPlaybackSpeedClicked() { + PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), + player.getPlaybackSkipSilence(), (speed, pitch, skipSilence) + -> player.setPlaybackParameters(speed, pitch, skipSilence)) + .show(getParentActivity().getSupportFragmentManager(), null); + } + + @Override + public boolean onLongClick(final View v) { + if (v.getId() == binding.moreOptionsButton.getId() && isFullscreen) { + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onMoreOptionsLongClicked); + hideControls(0, 0); + hideSystemUIIfNeeded(); + return true; + } + return super.onLongClick(v); + } + + @Override + public boolean onKeyDown(final int keyCode) { + if (keyCode == KeyEvent.KEYCODE_SPACE && isFullscreen) { + player.playPause(); + if (player.isPlaying()) { + hideControls(0, 0); + } + return true; + } + return super.onKeyDown(keyCode); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Video size, resize, orientation, fullscreen + //////////////////////////////////////////////////////////////////////////*/ + //region Video size, resize, orientation, fullscreen + + private void setupScreenRotationButton() { + binding.screenRotationButton.setVisibility(globalScreenOrientationLocked(context) + || isVerticalVideo || DeviceUtils.isTablet(context) + ? View.VISIBLE : View.GONE); + binding.screenRotationButton.setImageDrawable(AppCompatResources.getDrawable(context, + isFullscreen ? R.drawable.ic_fullscreen_exit + : R.drawable.ic_fullscreen)); + } + + @Override + public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { + super.onVideoSizeChanged(videoSize); + isVerticalVideo = videoSize.width < videoSize.height; + + if (globalScreenOrientationLocked(context) + && isFullscreen + && isLandscape() == isVerticalVideo + && !DeviceUtils.isTv(context) + && !DeviceUtils.isTablet(context)) { + // set correct orientation + player.getFragmentListener().ifPresent( + PlayerServiceEventListener::onScreenRotationButtonClicked); + } + + setupScreenRotationButton(); + } + + public void toggleFullscreen() { + if (DEBUG) { + Log.d(TAG, "toggleFullscreen() called"); + } + final PlayerServiceEventListener fragmentListener + = player.getFragmentListener().orElse(null); + if (fragmentListener == null || player.exoPlayerIsNull()) { + return; + } + + isFullscreen = !isFullscreen; + if (!isFullscreen) { + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait (open vertical video to reproduce) + binding.playbackControlRoot.setPadding(0, 0, 0, 0); + } else { + // Android needs tens milliseconds to send new insets but a user is able to see + // how controls changes it's position from `0` to `nav bar height` padding. + // So just hide the controls to hide this visual inconsistency + hideControls(0, 0); + } + fragmentListener.onFullscreenStateChanged(isFullscreen); + + if (isFullscreen) { + binding.titleTextView.setVisibility(View.VISIBLE); + binding.channelTextView.setVisibility(View.VISIBLE); + binding.playerCloseButton.setVisibility(View.GONE); + } else { + binding.titleTextView.setVisibility(View.GONE); + binding.channelTextView.setVisibility(View.GONE); + binding.playerCloseButton.setVisibility(View.VISIBLE); + } + setupScreenRotationButton(); + } + + public void checkLandscape() { + // check if landscape is correct + final boolean videoInLandscapeButNotInFullscreen + = isLandscape() && !isFullscreen && !player.isAudioOnly(); + final boolean notPaused = player.getCurrentState() != STATE_COMPLETED + && player.getCurrentState() != STATE_PAUSED; + + if (videoInLandscapeButNotInFullscreen + && notPaused + && !DeviceUtils.isTablet(context)) { + toggleFullscreen(); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Getters + //////////////////////////////////////////////////////////////////////////*/ + //region Getters + public PlayerBinding getBinding() { + return binding; + } + + public AppCompatActivity getParentActivity() { + return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext(); + } + + public boolean isLandscape() { + // DisplayMetrics from activity context knows about MultiWindow feature + // while DisplayMetrics from app context doesn't + return DeviceUtils.isLandscape(getParentActivity()); + } + //endregion +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java new file mode 100644 index 000000000..40c83c6c7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java @@ -0,0 +1,26 @@ +package org.schabi.newpipe.player.ui; + +import androidx.annotation.NonNull; + +import org.schabi.newpipe.player.NotificationUtil; +import org.schabi.newpipe.player.Player; + +public final class NotificationPlayerUi extends PlayerUi { + boolean foregroundNotificationAlreadyCreated = false; + + public NotificationPlayerUi(@NonNull final Player player) { + super(player); + } + + @Override + public void initPlayer() { + super.initPlayer(); + if (!foregroundNotificationAlreadyCreated) { + NotificationUtil.getInstance() + .createNotificationAndStartForeground(player, player.getService()); + foregroundNotificationAlreadyCreated = true; + } + } + + // TODO TODO on destroy remove foreground +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java new file mode 100644 index 000000000..fd63790d6 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -0,0 +1,120 @@ +package org.schabi.newpipe.player.ui; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player.RepeatMode; +import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.video.VideoSize; + +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.Player; + +import java.util.List; + +public abstract class PlayerUi { + private static final String TAG = PlayerUi.class.getSimpleName(); + + @NonNull protected Context context; + @NonNull protected Player player; + + public PlayerUi(@NonNull final Player player) { + this.context = player.getContext(); + this.player = player; + } + + @NonNull + public Player getPlayer() { + return player; + } + + + public void setupAfterIntent() { + } + + public void initPlayer() { + } + + public void initPlayback() { + } + + public void destroyPlayer() { + } + + public void destroy() { + } + + public void smoothStopForImmediateReusing() { + } + + public void onFragmentListenerSet() { + } + + public void onBroadcastReceived(final Intent intent) { + } + + public void onUpdateProgress(final int currentProgress, + final int duration, + final int bufferPercent) { + } + + public void onPrepared() { + } + + public void onBlocked() { + } + + public void onPlaying() { + } + + public void onBuffering() { + } + + public void onPaused() { + } + + public void onPausedSeek() { + } + + public void onCompleted() { + } + + public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + } + + public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { + } + + public void onMuteUnmuteChanged(final boolean isMuted) { + } + + public void onTextTracksChanged(@NonNull final Tracks currentTracks) { + } + + public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { + } + + public void onRenderedFirstFrame() { + } + + public void onCues(@NonNull final List cues) { + } + + public void onMetadataChanged(@NonNull final StreamInfo info) { + } + + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + } + + public void onPlayQueueEdited() { + } + + public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java new file mode 100644 index 000000000..8c5c0dbfa --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -0,0 +1,36 @@ +package org.schabi.newpipe.player.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +public final class PlayerUiList { + final List playerUis = new ArrayList<>(); + + public void add(final PlayerUi playerUi) { + playerUis.add(playerUi); + } + + public void destroyAll(final Class playerUiType) { + playerUis.stream() + .filter(playerUiType::isInstance) + .forEach(playerUi -> { + playerUi.destroyPlayer(); + playerUi.destroy(); + }); + playerUis.removeIf(playerUiType::isInstance); + } + + public Optional get(final Class playerUiType) { + return playerUis.stream() + .filter(playerUiType::isInstance) + .map(playerUiType::cast) + .findFirst(); + } + + public void call(final Consumer consumer) { + //noinspection SimplifyStreamApiCallChains + playerUis.stream().forEach(consumer); + } +} 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 new file mode 100644 index 000000000..b8a26a233 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PopupPlayerUi.java @@ -0,0 +1,460 @@ +package org.schabi.newpipe.player.ui; + +import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; +import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; +import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.AnticipateInterpolator; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.ui.SubtitleView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.PlayerBinding; +import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; +import org.schabi.newpipe.player.gesture.PopupPlayerGestureListener; +import org.schabi.newpipe.player.helper.PlayerHelper; + +public final class PopupPlayerUi extends VideoPlayerUi { + private static final String TAG = PopupPlayerUi.class.getSimpleName(); + + /*////////////////////////////////////////////////////////////////////////// + // Popup player + //////////////////////////////////////////////////////////////////////////*/ + + private PlayerPopupCloseOverlayBinding closeOverlayBinding; + + private boolean isPopupClosing = false; + + private int screenWidth; + private int screenHeight; + + /*////////////////////////////////////////////////////////////////////////// + // Popup player window manager + //////////////////////////////////////////////////////////////////////////*/ + + public static final int IDLE_WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + public static final int ONGOING_PLAYBACK_WINDOW_FLAGS = IDLE_WINDOW_FLAGS + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + + private WindowManager.LayoutParams popupLayoutParams; // null if player is not popup + private final WindowManager windowManager; + + public PopupPlayerUi(@NonNull final Player player, + @NonNull final PlayerBinding playerBinding) { + super(player, playerBinding); + windowManager = ContextCompat.getSystemService(context, WindowManager.class); + } + + @Override + public void setupAfterIntent() { + setupElementsVisibility(); + binding.getRoot().setVisibility(View.VISIBLE); + initPopup(); + initPopupCloseOverlay(); + binding.playPauseButton.requestFocus(); + } + + @Override + BasePlayerGestureListener buildGestureListener() { + return new PopupPlayerGestureListener(this); + } + + @SuppressLint("RtlHardcoded") + private void initPopup() { + if (DEBUG) { + Log.d(TAG, "initPopup() called"); + } + + // Popup is already added to windowManager + if (popupHasParent()) { + return; + } + + updateScreenSize(); + + popupLayoutParams = retrievePopupLayoutParamsFromPrefs(this); + binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); + + checkPopupPositionBounds(); + + binding.loadingPanel.setMinimumWidth(popupLayoutParams.width); + binding.loadingPanel.setMinimumHeight(popupLayoutParams.height); + + windowManager.addView(binding.getRoot(), popupLayoutParams); + + // Popup doesn't have aspectRatio selector, using FIT automatically + setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); + } + + @SuppressLint("RtlHardcoded") + private void initPopupCloseOverlay() { + if (DEBUG) { + Log.d(TAG, "initPopupCloseOverlay() called"); + } + + // closeOverlayView is already added to windowManager + if (closeOverlayBinding != null) { + return; + } + + closeOverlayBinding = PlayerPopupCloseOverlayBinding.inflate(LayoutInflater.from(context)); + + final WindowManager.LayoutParams closeOverlayLayoutParams = buildCloseOverlayLayoutParams(); + closeOverlayBinding.closeButton.setVisibility(View.GONE); + windowManager.addView(closeOverlayBinding.getRoot(), closeOverlayLayoutParams); + } + + @Override + protected void setupElementsVisibility() { + binding.fullScreenButton.setVisibility(View.VISIBLE); + binding.screenRotationButton.setVisibility(View.GONE); + binding.resizeTextView.setVisibility(View.GONE); + binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.GONE); + binding.queueButton.setVisibility(View.GONE); + binding.segmentsButton.setVisibility(View.GONE); + binding.moreOptionsButton.setVisibility(View.GONE); + binding.topControls.setOrientation(LinearLayout.HORIZONTAL); + binding.primaryControls.getLayoutParams().width + = LinearLayout.LayoutParams.WRAP_CONTENT; + binding.secondaryControls.setAlpha(1.0f); + binding.secondaryControls.setVisibility(View.VISIBLE); + binding.secondaryControls.setTranslationY(0); + binding.share.setVisibility(View.GONE); + binding.playWithKodi.setVisibility(View.GONE); + binding.openInBrowser.setVisibility(View.GONE); + binding.switchMute.setVisibility(View.GONE); + binding.playerCloseButton.setVisibility(View.GONE); + binding.topControls.bringToFront(); + binding.topControls.setClickable(false); + binding.topControls.setFocusable(false); + binding.bottomControls.bringToFront(); + super.setupElementsVisibility(); + } + + @Override + protected void setupElementsSize(final Resources resources) { + setupElementsSize( + 0, + 0, + resources.getDimensionPixelSize(R.dimen.player_popup_controls_padding), + resources.getDimensionPixelSize(R.dimen.player_popup_buttons_padding) + ); + } + + @Override + public void removeViewFromParent() { + // view was added by windowManager for popup player + windowManager.removeViewImmediate(binding.getRoot()); + } + + @Override + public void destroy() { + super.destroy(); + removePopupFromView(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Broadcast receiver + //////////////////////////////////////////////////////////////////////////*/ + //region Broadcast receiver + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + updateScreenSize(); + changePopupSize(popupLayoutParams.width); + checkPopupPositionBounds(); + } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + // Use only audio source when screen turns off while popup player is playing + if (player.isPlaying() || player.isLoading()) { + player.useVideoSource(false); + } + } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + // Restore video source when screen turns on and user is watching video in popup player + if (player.isPlaying() || player.isLoading()) { + player.useVideoSource(true); + } + } + } + //endregion + + + /** + * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary + * that goes from (0, 0) to (screenWidth, screenHeight). + *

+ * If it's out of these boundaries, {@link #popupLayoutParams}' position is changed + * and {@code true} is returned to represent this change. + *

+ */ + public void checkPopupPositionBounds() { + if (DEBUG) { + Log.d(TAG, "checkPopupPositionBounds() called with: " + + "screenWidth = [" + screenWidth + "], " + + "screenHeight = [" + screenHeight + "]"); + } + if (popupLayoutParams == null) { + return; + } + + if (popupLayoutParams.x < 0) { + popupLayoutParams.x = 0; + } else if (popupLayoutParams.x > screenWidth - popupLayoutParams.width) { + popupLayoutParams.x = screenWidth - popupLayoutParams.width; + } + + if (popupLayoutParams.y < 0) { + popupLayoutParams.y = 0; + } else if (popupLayoutParams.y > screenHeight - popupLayoutParams.height) { + popupLayoutParams.y = screenHeight - popupLayoutParams.height; + } + } + + public void updateScreenSize() { + final DisplayMetrics metrics = new DisplayMetrics(); + windowManager.getDefaultDisplay().getMetrics(metrics); + + screenWidth = metrics.widthPixels; + screenHeight = metrics.heightPixels; + if (DEBUG) { + Log.d(TAG, "updateScreenSize() called: screenWidth = [" + + screenWidth + "], screenHeight = [" + screenHeight + "]"); + } + } + + /** + * Changes the size of the popup based on the width. + * @param width the new width, height is calculated with + * {@link PlayerHelper#getMinimumVideoHeight(float)} + */ + public void changePopupSize(final int width) { + if (DEBUG) { + Log.d(TAG, "changePopupSize() called with: width = [" + width + "]"); + } + + if (anyPopupViewIsNull()) { + return; + } + + final float minimumWidth = context.getResources().getDimension(R.dimen.popup_minimum_width); + final int actualWidth = (int) (width > screenWidth ? screenWidth + : (width < minimumWidth ? minimumWidth : width)); + final int actualHeight = (int) getMinimumVideoHeight(width); + if (DEBUG) { + Log.d(TAG, "updatePopupSize() updated values:" + + " width = [" + actualWidth + "], height = [" + actualHeight + "]"); + } + + popupLayoutParams.width = actualWidth; + popupLayoutParams.height = actualHeight; + binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); + windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); + } + + private void changePopupWindowFlags(final int flags) { + if (DEBUG) { + Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); + } + + if (!anyPopupViewIsNull()) { + popupLayoutParams.flags = flags; + windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); + } + } + + public void closePopup() { + if (DEBUG) { + Log.d(TAG, "closePopup() called, isPopupClosing = " + isPopupClosing); + } + if (isPopupClosing) { + return; + } + isPopupClosing = true; + + player.saveStreamProgressState(); + windowManager.removeView(binding.getRoot()); + + animatePopupOverlayAndFinishService(); + } + + public boolean isPopupClosing() { + return isPopupClosing; + } + + public void removePopupFromView() { + if (windowManager != null) { + // wrap in try-catch since it could sometimes generate errors randomly + try { + if (popupHasParent()) { + windowManager.removeView(binding.getRoot()); + } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup from window manager", e); + } + + try { + final boolean closeOverlayHasParent = closeOverlayBinding != null + && closeOverlayBinding.getRoot().getParent() != null; + if (closeOverlayHasParent) { + windowManager.removeView(closeOverlayBinding.getRoot()); + } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup overlay from window manager", e); + } + } + } + + private void animatePopupOverlayAndFinishService() { + final int targetTranslationY = + (int) (closeOverlayBinding.closeButton.getRootView().getHeight() + - closeOverlayBinding.closeButton.getY()); + + closeOverlayBinding.closeButton.animate().setListener(null).cancel(); + closeOverlayBinding.closeButton.animate() + .setInterpolator(new AnticipateInterpolator()) + .translationY(targetTranslationY) + .setDuration(400) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationCancel(final Animator animation) { + end(); + } + + @Override + public void onAnimationEnd(final Animator animation) { + end(); + } + + private void end() { + windowManager.removeView(closeOverlayBinding.getRoot()); + closeOverlayBinding = null; + player.getService().stopService(); + } + }).start(); + } + + @Override + protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { + // no need for the end screen thumbnail to be resized on popup player: it's only needed + // for the main player so that it is enlarged correctly inside the fragment + return bitmap.getHeight(); + } + + private boolean popupHasParent() { + return binding != null + && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams + && binding.getRoot().getParent() != null; + } + + private boolean anyPopupViewIsNull() { + return popupLayoutParams == null || windowManager == null + || binding.getRoot().getParent() == null; + } + + @Override + public void onPlaying() { + super.onPlaying(); + changePopupWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS); + } + + @Override + public void onPaused() { + super.onPaused(); + changePopupWindowFlags(IDLE_WINDOW_FLAGS); + } + + @Override + public void onCompleted() { + super.onCompleted(); + changePopupWindowFlags(IDLE_WINDOW_FLAGS); + } + + @Override + protected void setupSubtitleView(final float captionScale) { + final float captionRatio = (captionScale - 1.0f) / 5.0f + 1.0f; + binding.subtitleView.setFractionalTextSize( + SubtitleView.DEFAULT_TEXT_SIZE_FRACTION * captionRatio); + } + + @Override + protected void onPlaybackSpeedClicked() { + playbackSpeedPopupMenu.show(); + isSomePopupMenuVisible = true; + } + + /*////////////////////////////////////////////////////////////////////////// + // Gestures + //////////////////////////////////////////////////////////////////////////*/ + //region Gestures + private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) { + final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft() + + closeOverlayBinding.closeButton.getWidth() / 2; + final int closeOverlayButtonY = closeOverlayBinding.closeButton.getTop() + + closeOverlayBinding.closeButton.getHeight() / 2; + + final float fingerX = popupLayoutParams.x + popupMotionEvent.getX(); + final float fingerY = popupLayoutParams.y + popupMotionEvent.getY(); + + return (int) Math.sqrt(Math.pow(closeOverlayButtonX - fingerX, 2) + + Math.pow(closeOverlayButtonY - fingerY, 2)); + } + + private float getClosingRadius() { + final int buttonRadius = closeOverlayBinding.closeButton.getWidth() / 2; + // 20% wider than the button itself + return buttonRadius * 1.2f; + } + + public boolean isInsideClosingRadius(@NonNull final MotionEvent popupMotionEvent) { + return distanceFromCloseButton(popupMotionEvent) <= getClosingRadius(); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Getters + //////////////////////////////////////////////////////////////////////////*/ + //region Gestures + public PlayerPopupCloseOverlayBinding getCloseOverlayBinding() { + return closeOverlayBinding; + } + + public WindowManager.LayoutParams getPopupLayoutParams() { + return popupLayoutParams; + } + + public WindowManager getWindowManager() { + return windowManager; + } + + public int getScreenHeight() { + return screenHeight; + } + + public int getScreenWidth() { + return screenWidth; + } + //endregion +} diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java new file mode 100644 index 000000000..99ecb5540 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -0,0 +1,1523 @@ +package org.schabi.newpipe.player.ui; + +import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; +import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; +import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; +import static org.schabi.newpipe.MainActivity.DEBUG; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; +import static org.schabi.newpipe.player.Player.RENDERER_UNAVAILABLE; +import static org.schabi.newpipe.player.Player.STATE_BUFFERING; +import static org.schabi.newpipe.player.Player.STATE_COMPLETED; +import static org.schabi.newpipe.player.Player.STATE_PAUSED; +import static org.schabi.newpipe.player.Player.STATE_PAUSED_SEEK; +import static org.schabi.newpipe.player.Player.STATE_PLAYING; +import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; +import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; +import static org.schabi.newpipe.player.helper.PlayerHelper.nextResizeModeAndSaveToPrefs; +import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; + +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.util.Log; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.Surface; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.SeekBar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.view.ContextThemeWrapper; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.appcompat.widget.PopupMenu; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.PlaybackParameters; +import com.google.android.exoplayer2.Player.RepeatMode; +import com.google.android.exoplayer2.Tracks; +import com.google.android.exoplayer2.text.Cue; +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; +import com.google.android.exoplayer2.ui.CaptionStyleCompat; +import com.google.android.exoplayer2.video.VideoSize; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.PlayerBinding; +import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.VideoStream; +import org.schabi.newpipe.fragments.detail.VideoDetailFragment; +import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; +import org.schabi.newpipe.player.gesture.DisplayPortion; +import org.schabi.newpipe.player.helper.PlayerHelper; +import org.schabi.newpipe.player.mediaitem.MediaItemTag; +import org.schabi.newpipe.player.playback.SurfaceHolderCallback; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; +import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHelper; +import org.schabi.newpipe.player.seekbarpreview.SeekbarPreviewThumbnailHolder; +import org.schabi.newpipe.util.DeviceUtils; +import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.external_communication.KoreUtils; +import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.views.player.PlayerFastSeekOverlay; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public abstract class VideoPlayerUi extends PlayerUi + implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { + private static final String TAG = VideoPlayerUi.class.getSimpleName(); + + // time constants + public static final long DEFAULT_CONTROLS_DURATION = 300; // 300 millis + public static final long DEFAULT_CONTROLS_HIDE_TIME = 2000; // 2 Seconds + public static final long DPAD_CONTROLS_HIDE_TIME = 7000; // 7 Seconds + public static final int SEEK_OVERLAY_DURATION = 450; // 450 millis + + // other constants (TODO remove playback speeds and use normal menu for popup, too) + private static final float[] PLAYBACK_SPEEDS = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f}; + + + /*////////////////////////////////////////////////////////////////////////// + // Views + //////////////////////////////////////////////////////////////////////////*/ + + protected PlayerBinding binding; + private final Handler controlsVisibilityHandler = new Handler(); + @Nullable private SurfaceHolderCallback surfaceHolderCallback; + @Nullable private Bitmap thumbnail = null; + + + /*////////////////////////////////////////////////////////////////////////// + // Popup menus ("popup" means that they pop up, not that they belong to the popup player) + //////////////////////////////////////////////////////////////////////////*/ + + private static final int POPUP_MENU_ID_QUALITY = 69; + private static final int POPUP_MENU_ID_PLAYBACK_SPEED = 79; + private static final int POPUP_MENU_ID_CAPTION = 89; + + protected boolean isSomePopupMenuVisible = false; + private PopupMenu qualityPopupMenu; + protected PopupMenu playbackSpeedPopupMenu; + private PopupMenu captionPopupMenu; + + + /*////////////////////////////////////////////////////////////////////////// + // Gestures + //////////////////////////////////////////////////////////////////////////*/ + + private GestureDetector gestureDetector; + private BasePlayerGestureListener playerGestureListener; + + @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = + new SeekbarPreviewThumbnailHolder(); + + public VideoPlayerUi(@NonNull final Player player, + @NonNull final PlayerBinding playerBinding) { + super(player); + binding = playerBinding; + } + + + /*////////////////////////////////////////////////////////////////////////// + // Setup + //////////////////////////////////////////////////////////////////////////*/ + //region Setup + public void setupFromView() { + initViews(); + initListeners(); + setupPlayerSeekOverlay(); + } + + private void initViews() { + setupSubtitleView(); + + binding.resizeTextView + .setText(PlayerHelper.resizeTypeOf(context, binding.surfaceView.getResizeMode())); + + binding.playbackSeekBar.getThumb() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); + binding.playbackSeekBar.getProgressDrawable() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)); + + final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, + R.style.DarkPopupMenu); + + qualityPopupMenu = new PopupMenu(themeWrapper, binding.qualityTextView); + playbackSpeedPopupMenu = new PopupMenu(context, binding.playbackSpeed); + captionPopupMenu = new PopupMenu(themeWrapper, binding.captionTextView); + + binding.progressBarLoadingPanel.getIndeterminateDrawable() + .setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY)); + + binding.titleTextView.setSelected(true); + binding.channelTextView.setSelected(true); + + // Prevent hiding of bottom sheet via swipe inside queue + binding.itemsList.setNestedScrollingEnabled(false); + } + + abstract BasePlayerGestureListener buildGestureListener(); + + protected void initListeners() { + binding.qualityTextView.setOnClickListener(this); + binding.playbackSpeed.setOnClickListener(this); + + binding.playbackSeekBar.setOnSeekBarChangeListener(this); + binding.captionTextView.setOnClickListener(this); + binding.resizeTextView.setOnClickListener(this); + binding.playbackLiveSync.setOnClickListener(this); + + playerGestureListener = buildGestureListener(); + gestureDetector = new GestureDetector(context, playerGestureListener); + binding.getRoot().setOnTouchListener(playerGestureListener); + + binding.repeatButton.setOnClickListener(v -> onRepeatClicked()); + binding.shuffleButton.setOnClickListener(v -> onShuffleClicked()); + + binding.playPauseButton.setOnClickListener(this); + binding.playPreviousButton.setOnClickListener(this); + binding.playNextButton.setOnClickListener(this); + + binding.moreOptionsButton.setOnClickListener(this); + binding.moreOptionsButton.setOnLongClickListener(this); + binding.share.setOnClickListener(this); + binding.share.setOnLongClickListener(this); + binding.fullScreenButton.setOnClickListener(this); + binding.screenRotationButton.setOnClickListener(this); + binding.playWithKodi.setOnClickListener(this); + binding.openInBrowser.setOnClickListener(this); + binding.playerCloseButton.setOnClickListener(this); + binding.switchMute.setOnClickListener(this); + + ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, (view, windowInsets) -> { + final Insets cutout = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()); + if (!cutout.equals(Insets.NONE)) { + view.setPadding(cutout.left, cutout.top, cutout.right, cutout.bottom); + } + return windowInsets; + }); + + // PlaybackControlRoot already consumed window insets but we should pass them to + // player_overlays and fast_seek_overlay too. Without it they will be off-centered. + binding.playbackControlRoot.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + binding.playerOverlays.setPadding( + v.getPaddingLeft(), + v.getPaddingTop(), + v.getPaddingRight(), + v.getPaddingBottom()); + + // If we added padding to the fast seek overlay, too, it would not go under the + // system ui. Instead we apply negative margins equal to the window insets of + // the opposite side, so that the view covers all of the player (overflowing on + // some sides) and its center coincides with the center of other controls. + final RelativeLayout.LayoutParams fastSeekParams = (RelativeLayout.LayoutParams) + binding.fastSeekOverlay.getLayoutParams(); + fastSeekParams.leftMargin = -v.getPaddingRight(); + fastSeekParams.topMargin = -v.getPaddingBottom(); + fastSeekParams.rightMargin = -v.getPaddingLeft(); + fastSeekParams.bottomMargin = -v.getPaddingTop(); + }); + } + + /** + * Initializes the Fast-For/Backward overlay. + */ + private void setupPlayerSeekOverlay() { + binding.fastSeekOverlay + .seekSecondsSupplier(() -> retrieveSeekDurationFromPreferences(player) / 1000) + .performListener(new PlayerFastSeekOverlay.PerformListener() { + + @Override + public void onDoubleTap() { + animate(binding.fastSeekOverlay, true, SEEK_OVERLAY_DURATION); + } + + @Override + public void onDoubleTapEnd() { + animate(binding.fastSeekOverlay, false, SEEK_OVERLAY_DURATION); + } + + @NonNull + @Override + public FastSeekDirection getFastSeekDirection( + @NonNull final DisplayPortion portion + ) { + if (player.exoPlayerIsNull()) { + // Abort seeking + playerGestureListener.endMultiDoubleTap(); + return FastSeekDirection.NONE; + } + if (portion == DisplayPortion.LEFT) { + // Check if it's possible to rewind + // Small puffer to eliminate infinite rewind seeking + if (player.getExoPlayer().getCurrentPosition() < 500L) { + return FastSeekDirection.NONE; + } + return FastSeekDirection.BACKWARD; + } else if (portion == DisplayPortion.RIGHT) { + // Check if it's possible to fast-forward + if (player.getCurrentState() == STATE_COMPLETED + || player.getExoPlayer().getCurrentPosition() + >= player.getExoPlayer().getDuration()) { + return FastSeekDirection.NONE; + } + return FastSeekDirection.FORWARD; + } + /* portion == DisplayPortion.MIDDLE */ + return FastSeekDirection.NONE; + } + + @Override + public void seek(final boolean forward) { + playerGestureListener.keepInDoubleTapMode(); + if (forward) { + player.fastForward(); + } else { + player.fastRewind(); + } + } + }); + playerGestureListener.doubleTapControls(binding.fastSeekOverlay); + } + + @Override + public void setupAfterIntent() { + super.setupAfterIntent(); + setupElementsVisibility(); + setupElementsSize(context.getResources()); + } + + @Override + public void initPlayer() { + super.initPlayer(); + setupVideoSurface(); + setupFromView(); + } + + @Override + public void initPlayback() { + super.initPlayback(); + + // #6825 - Ensure that the shuffle-button is in the correct state on the UI + setShuffleButton(player.getExoPlayer().getShuffleModeEnabled()); + } + + public abstract void removeViewFromParent(); + + @Override + public void destroyPlayer() { + super.destroyPlayer(); + cleanupVideoSurface(); + } + + @Override + public void destroy() { + super.destroy(); + if (binding != null) { + binding.endScreen.setImageBitmap(null); + } + } + + protected void setupElementsVisibility() { + setMuteButton(player.isMuted()); + animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0); + } + + protected abstract void setupElementsSize(Resources resources); + + protected void setupElementsSize(final int buttonsMinWidth, + final int playerTopPad, + final int controlsPad, + final int buttonsPad) { + binding.topControls.setPaddingRelative(controlsPad, playerTopPad, controlsPad, 0); + binding.bottomControls.setPaddingRelative(controlsPad, 0, controlsPad, 0); + binding.qualityTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + binding.playbackSpeed.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + binding.playbackSpeed.setMinimumWidth(buttonsMinWidth); + binding.captionTextView.setPadding(buttonsPad, buttonsPad, buttonsPad, buttonsPad); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Broadcast receiver + //////////////////////////////////////////////////////////////////////////*/ + //region Broadcast receiver + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + // When the orientation changed, the screen height might be smaller. + // If the end screen thumbnail is not re-scaled, + // it can be larger than the current screen height + // and thus enlarging the whole player. + // This causes the seekbar to be ouf the visible area. + updateEndScreenThumbnail(); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Thumbnail + //////////////////////////////////////////////////////////////////////////*/ + //region Thumbnail + /** + * Scale the player audio / end screen thumbnail down if necessary. + *

+ * This is necessary when the thumbnail's height is larger than the device's height + * and thus is enlarging the player's height + * causing the bottom playback controls to be out of the visible screen. + *

+ */ + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + thumbnail = bitmap; + updateEndScreenThumbnail(); + } + + private void updateEndScreenThumbnail() { + if (thumbnail == null) { + // remove end screen thumbnail + binding.endScreen.setImageDrawable(null); + return; + } + + final float endScreenHeight = calculateMaxEndScreenThumbnailHeight(thumbnail); + final Bitmap endScreenBitmap = Bitmap.createScaledBitmap( + thumbnail, + (int) (thumbnail.getWidth() / (thumbnail.getHeight() / endScreenHeight)), + (int) endScreenHeight, + true); + + if (DEBUG) { + Log.d(TAG, "Thumbnail - onThumbnailLoaded() called with: " + + "currentThumbnail = [" + thumbnail + "], " + + thumbnail.getWidth() + "x" + thumbnail.getHeight() + + ", scaled end screen height = " + endScreenHeight + + ", scaled end screen width = " + endScreenBitmap.getWidth()); + } + + binding.endScreen.setImageBitmap(endScreenBitmap); + } + + protected abstract float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap); + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Progress loop and updates + //////////////////////////////////////////////////////////////////////////*/ + //region Progress loop and updates + @Override + public void onUpdateProgress(final int currentProgress, + final int duration, + final int bufferPercent) { + + if (duration != binding.playbackSeekBar.getMax()) { + setVideoDurationToControls(duration); + } + if (player.getCurrentState() != STATE_PAUSED) { + updatePlayBackElementsCurrentDuration(currentProgress); + } + if (player.isLoading() || bufferPercent > 90) { + binding.playbackSeekBar.setSecondaryProgress( + (int) (binding.playbackSeekBar.getMax() * ((float) bufferPercent / 100))); + } + if (DEBUG && bufferPercent % 20 == 0) { //Limit log + Log.d(TAG, "notifyProgressUpdateToListeners() called with: " + + "isVisible = " + isControlsVisible() + ", " + + "currentProgress = [" + currentProgress + "], " + + "duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]"); + } + binding.playbackLiveSync.setClickable(!player.isLiveEdge()); + } + + /** + * Sets the current duration into the corresponding elements. + */ + private void updatePlayBackElementsCurrentDuration(final int currentProgress) { + // Don't set seekbar progress while user is seeking + if (player.getCurrentState() != STATE_PAUSED_SEEK) { + binding.playbackSeekBar.setProgress(currentProgress); + } + binding.playbackCurrentTime.setText(getTimeString(currentProgress)); + } + + /** + * Sets the video duration time into all control components (e.g. seekbar). + */ + private void setVideoDurationToControls(final int duration) { + binding.playbackEndTime.setText(getTimeString(duration)); + + binding.playbackSeekBar.setMax(duration); + // This is important for Android TVs otherwise it would apply the default from + // setMax/Min methods which is (max - min) / 20 + binding.playbackSeekBar.setKeyProgressIncrement( + PlayerHelper.retrieveSeekDurationFromPreferences(player)); + } + + @Override // seekbar listener + public void onProgressChanged(final SeekBar seekBar, final int progress, + final boolean fromUser) { + // Currently we don't need method execution when fromUser is false + if (!fromUser) { + return; + } + if (DEBUG) { + Log.d(TAG, "onProgressChanged() called with: " + + "seekBar = [" + seekBar + "], progress = [" + progress + "]"); + } + + binding.currentDisplaySeek.setText(getTimeString(progress)); + + // Seekbar Preview Thumbnail + SeekbarPreviewThumbnailHelper + .tryResizeAndSetSeekbarPreviewThumbnail( + player.getContext(), + seekbarPreviewThumbnailHolder.getBitmapAt(progress), + binding.currentSeekbarPreviewThumbnail, + binding.subtitleView::getWidth); + + adjustSeekbarPreviewContainer(); + } + + + private void adjustSeekbarPreviewContainer() { + try { + // Should only be required when an error occurred before + // and the layout was positioned in the center + binding.bottomSeekbarPreviewLayout.setGravity(Gravity.NO_GRAVITY); + + // Calculate the current left position of seekbar progress in px + // More info: https://stackoverflow.com/q/20493577 + final int currentSeekbarLeft = + binding.playbackSeekBar.getLeft() + + binding.playbackSeekBar.getPaddingLeft() + + binding.playbackSeekBar.getThumb().getBounds().left; + + // Calculate the (unchecked) left position of the container + final int uncheckedContainerLeft = + currentSeekbarLeft - (binding.seekbarPreviewContainer.getWidth() / 2); + + // Fix the position so it's within the boundaries + final int checkedContainerLeft = + Math.max( + Math.min( + uncheckedContainerLeft, + // Max left + binding.playbackWindowRoot.getWidth() + - binding.seekbarPreviewContainer.getWidth() + ), + 0 // Min left + ); + + // See also: https://stackoverflow.com/a/23249734 + final LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams( + binding.seekbarPreviewContainer.getLayoutParams()); + params.setMarginStart(checkedContainerLeft); + binding.seekbarPreviewContainer.setLayoutParams(params); + } catch (final Exception ex) { + Log.e(TAG, "Failed to adjust seekbarPreviewContainer", ex); + // Fallback - position in the middle + binding.bottomSeekbarPreviewLayout.setGravity(Gravity.CENTER); + } + } + + @Override // seekbar listener + public void onStartTrackingTouch(final SeekBar seekBar) { + if (DEBUG) { + Log.d(TAG, "onStartTrackingTouch() called with: seekBar = [" + seekBar + "]"); + } + if (player.getCurrentState() != STATE_PAUSED_SEEK) { + player.changeState(STATE_PAUSED_SEEK); + } + + player.saveWasPlaying(); + if (player.isPlaying()) { + player.getExoPlayer().pause(); + } + + showControls(0); + animate(binding.currentDisplaySeek, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SCALE_AND_ALPHA); + animate(binding.currentSeekbarPreviewThumbnail, true, DEFAULT_CONTROLS_DURATION, + AnimationType.SCALE_AND_ALPHA); + } + + @Override // seekbar listener + public void onStopTrackingTouch(final SeekBar seekBar) { + if (DEBUG) { + Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]"); + } + + player.seekTo(seekBar.getProgress()); + if (player.wasPlaying() || player.getExoPlayer().getDuration() == seekBar.getProgress()) { + player.getExoPlayer().play(); + } + + binding.playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); + animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); + animate(binding.currentSeekbarPreviewThumbnail, false, 200, AnimationType.SCALE_AND_ALPHA); + + if (player.getCurrentState() == STATE_PAUSED_SEEK) { + player.changeState(STATE_BUFFERING); + } + if (!player.isProgressLoopRunning()) { + player.startProgressLoop(); + } + if (player.wasPlaying()) { + showControlsThenHide(); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Controls showing / hiding + //////////////////////////////////////////////////////////////////////////*/ + //region Controls showing / hiding + + public boolean isControlsVisible() { + return binding != null && binding.playbackControlRoot.getVisibility() == View.VISIBLE; + } + + public void showControlsThenHide() { + if (DEBUG) { + Log.d(TAG, "showControlsThenHide() called"); + } + + showOrHideButtons(); + showSystemUIPartially(); + + final long hideTime = binding.playbackControlRoot.isInTouchMode() + ? DEFAULT_CONTROLS_HIDE_TIME + : DPAD_CONTROLS_HIDE_TIME; + + showHideShadow(true, DEFAULT_CONTROLS_DURATION); + animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, + AnimationType.ALPHA, 0, () -> hideControls(DEFAULT_CONTROLS_DURATION, hideTime)); + } + + public void showControls(final long duration) { + if (DEBUG) { + Log.d(TAG, "showControls() called"); + } + showOrHideButtons(); + showSystemUIPartially(); + controlsVisibilityHandler.removeCallbacksAndMessages(null); + showHideShadow(true, duration); + animate(binding.playbackControlRoot, true, duration); + } + + public void hideControls(final long duration, final long delay) { + if (DEBUG) { + Log.d(TAG, "hideControls() called with: duration = [" + duration + + "], delay = [" + delay + "]"); + } + + showOrHideButtons(); + + controlsVisibilityHandler.removeCallbacksAndMessages(null); + controlsVisibilityHandler.postDelayed(() -> { + showHideShadow(false, duration); + animate(binding.playbackControlRoot, false, duration, AnimationType.ALPHA, + 0, this::hideSystemUIIfNeeded); + }, delay); + } + + public void showHideShadow(final boolean show, final long duration) { + animate(binding.playbackControlsShadow, show, duration, AnimationType.ALPHA, 0, null); + animate(binding.playerTopShadow, show, duration, AnimationType.ALPHA, 0, null); + animate(binding.playerBottomShadow, show, duration, AnimationType.ALPHA, 0, null); + } + + protected void showOrHideButtons() { + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + final boolean showPrev = playQueue.getIndex() != 0; + final boolean showNext = playQueue.getIndex() + 1 != playQueue.getStreams().size(); + + binding.playPreviousButton.setVisibility(showPrev ? View.VISIBLE : View.INVISIBLE); + binding.playPreviousButton.setAlpha(showPrev ? 1.0f : 0.0f); + binding.playNextButton.setVisibility(showNext ? View.VISIBLE : View.INVISIBLE); + binding.playNextButton.setAlpha(showNext ? 1.0f : 0.0f); + } + + protected void showSystemUIPartially() { + // system UI is really changed only by MainPlayerUi, so overridden there + } + + protected void hideSystemUIIfNeeded() { + // system UI is really changed only by MainPlayerUi, so overridden there + } + + protected boolean isAnyListViewOpen() { + // only MainPlayerUi has list views for the queue and for segments, so overridden there + return false; + } + + public boolean isFullscreen() { + // only MainPlayerUi can be in fullscreen, so overridden there + return false; + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Playback states + //////////////////////////////////////////////////////////////////////////*/ + //region Playback states + @Override + public void onPrepared() { + super.onPrepared(); + setVideoDurationToControls((int) player.getExoPlayer().getDuration()); + binding.playbackSpeed.setText(formatSpeed(player.getPlaybackSpeed())); + } + + @Override + public void onBlocked() { + super.onBlocked(); + + // if we are e.g. switching players, hide controls + hideControls(DEFAULT_CONTROLS_DURATION, 0); + + binding.playbackSeekBar.setEnabled(false); + binding.playbackSeekBar.getThumb() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); + + binding.loadingPanel.setBackgroundColor(Color.BLACK); + animate(binding.loadingPanel, true, 0); + animate(binding.surfaceForeground, true, 100); + + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); + animatePlayButtons(false, 100); + binding.getRoot().setKeepScreenOn(false); + } + + @Override + public void onPlaying() { + super.onPlaying(); + + updateStreamRelatedViews(); + + binding.playbackSeekBar.setEnabled(true); + binding.playbackSeekBar.getThumb() + .setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)); + + binding.loadingPanel.setVisibility(View.GONE); + + animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); + + animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, + () -> { + binding.playPauseButton.setImageResource(R.drawable.ic_pause); + animatePlayButtons(true, 200); + if (!isAnyListViewOpen()) { + binding.playPauseButton.requestFocus(); + } + }); + + binding.getRoot().setKeepScreenOn(true); + } + + @Override + public void onBuffering() { + super.onBuffering(); + binding.loadingPanel.setBackgroundColor(Color.TRANSPARENT); + binding.loadingPanel.setVisibility(View.VISIBLE); + binding.getRoot().setKeepScreenOn(true); + } + + @Override + public void onPaused() { + super.onPaused(); + + // Don't let UI elements popup during double tap seeking. This state is entered sometimes + // during seeking/loading. This if-else check ensures that the controls aren't popping up. + if (!playerGestureListener.isDoubleTapping()) { + showControls(400); + binding.loadingPanel.setVisibility(View.GONE); + + animate(binding.playPauseButton, false, 80, AnimationType.SCALE_AND_ALPHA, 0, + () -> { + binding.playPauseButton.setImageResource(R.drawable.ic_play_arrow); + animatePlayButtons(true, 200); + if (!isAnyListViewOpen()) { + binding.playPauseButton.requestFocus(); + } + }); + } + + binding.getRoot().setKeepScreenOn(false); + } + + @Override + public void onPausedSeek() { + super.onPausedSeek(); + animatePlayButtons(false, 100); + binding.getRoot().setKeepScreenOn(true); + } + + @Override + public void onCompleted() { + super.onCompleted(); + + animate(binding.playPauseButton, false, 0, AnimationType.SCALE_AND_ALPHA, 0, + () -> { + binding.playPauseButton.setImageResource(R.drawable.ic_replay); + animatePlayButtons(true, DEFAULT_CONTROLS_DURATION); + }); + + binding.getRoot().setKeepScreenOn(false); + + // When a (short) video ends the elements have to display the correct values - see #6180 + updatePlayBackElementsCurrentDuration(binding.playbackSeekBar.getMax()); + + showControls(500); + animate(binding.currentDisplaySeek, false, 200, AnimationType.SCALE_AND_ALPHA); + binding.loadingPanel.setVisibility(View.GONE); + animate(binding.surfaceForeground, true, 100); + } + + private void animatePlayButtons(final boolean show, final long duration) { + animate(binding.playPauseButton, show, duration, AnimationType.SCALE_AND_ALPHA); + + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + if (playQueue == null) { + return; + } + + if (!show || playQueue.getIndex() > 0) { + animate( + binding.playPreviousButton, + show, + duration, + AnimationType.SCALE_AND_ALPHA); + } + if (!show || playQueue.getIndex() + 1 < playQueue.getStreams().size()) { + animate( + binding.playNextButton, + show, + duration, + AnimationType.SCALE_AND_ALPHA); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Repeat, shuffle, mute + //////////////////////////////////////////////////////////////////////////*/ + //region Repeat and shuffle + public void onRepeatClicked() { + if (DEBUG) { + Log.d(TAG, "onRepeatClicked() called"); + } + player.cycleNextRepeatMode(); + } + + public void onShuffleClicked() { + if (DEBUG) { + Log.d(TAG, "onShuffleClicked() called"); + } + player.toggleShuffleModeEnabled(); + } + + @Override + public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + super.onRepeatModeChanged(repeatMode); + setRepeatModeButton(binding.repeatButton, repeatMode); + } + + @Override + public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { + super.onShuffleModeEnabledChanged(shuffleModeEnabled); + setShuffleButton(shuffleModeEnabled); + } + + @Override + public void onMuteUnmuteChanged(final boolean isMuted) { + super.onMuteUnmuteChanged(isMuted); + setMuteButton(isMuted); + } + + private void setRepeatModeButton(final AppCompatImageButton imageButton, + @RepeatMode final int repeatMode) { + switch (repeatMode) { + case REPEAT_MODE_OFF: + imageButton.setImageResource(R.drawable.exo_controls_repeat_off); + break; + case REPEAT_MODE_ONE: + imageButton.setImageResource(R.drawable.exo_controls_repeat_one); + break; + case REPEAT_MODE_ALL: + imageButton.setImageResource(R.drawable.exo_controls_repeat_all); + break; + } + } + + private void setMuteButton(final boolean isMuted) { + binding.switchMute.setImageDrawable(AppCompatResources.getDrawable(context, isMuted + ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); + } + + private void setShuffleButton(final boolean shuffled) { + binding.shuffleButton.setImageAlpha(shuffled ? 255 : 77); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // ExoPlayer listeners (that didn't fit in other categories) + //////////////////////////////////////////////////////////////////////////*/ + //region ExoPlayer listeners (that didn't fit in other categories) + @Override + public void onTextTracksChanged(@NonNull final Tracks currentTracks) { + super.onTextTracksChanged(currentTracks); + + final boolean trackTypeTextSupported = !currentTracks.containsType(C.TRACK_TYPE_TEXT) + || currentTracks.isTypeSupported(C.TRACK_TYPE_TEXT, false); + if (getPlayer().getTrackSelector().getCurrentMappedTrackInfo() == null + || !trackTypeTextSupported) { + binding.captionTextView.setVisibility(View.GONE); + return; + } + + // Extract all loaded languages + final List textTracks = currentTracks + .getGroups() + .stream() + .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) + .collect(Collectors.toList()); + final List availableLanguages = textTracks.stream() + .map(Tracks.Group::getMediaTrackGroup) + .filter(textTrack -> textTrack.length > 0) + .map(textTrack -> textTrack.getFormat(0).language) + .collect(Collectors.toList()); + + // Find selected text track + final Optional selectedTracks = textTracks.stream() + .filter(Tracks.Group::isSelected) + .filter(info -> info.getMediaTrackGroup().length >= 1) + .map(info -> info.getMediaTrackGroup().getFormat(0)) + .findFirst(); + + // Build UI + buildCaptionMenu(availableLanguages); + //noinspection SimplifyOptionalCallChains + if (player.getTrackSelector().getParameters().getRendererDisabled( + player.getCaptionRendererIndex()) || !selectedTracks.isPresent()) { + binding.captionTextView.setText(R.string.caption_none); + } else { + binding.captionTextView.setText(selectedTracks.get().language); + } + binding.captionTextView.setVisibility( + availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); + } + + @Override + public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { + super.onPlaybackParametersChanged(playbackParameters); + binding.playbackSpeed.setText(formatSpeed(playbackParameters.speed)); + } + + @Override + public void onRenderedFirstFrame() { + super.onRenderedFirstFrame(); + //TODO check if this causes black screen when switching to fullscreen + animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION); + } + + @Override + public void onCues(@NonNull List cues) { + super.onCues(cues); + binding.subtitleView.setCues(cues); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Metadata & stream related views + //////////////////////////////////////////////////////////////////////////*/ + //region Metadata & stream related views + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + + updateStreamRelatedViews(); + + binding.titleTextView.setText(info.getName()); + binding.channelTextView.setText(info.getUploaderName()); + + this.seekbarPreviewThumbnailHolder.resetFrom(player.getContext(), info.getPreviewFrames()); + } + + private void updateStreamRelatedViews() { + //noinspection SimplifyOptionalCallChains + if (!player.getCurrentStreamInfo().isPresent()) { + return; + } + final StreamInfo info = player.getCurrentStreamInfo().get(); + + binding.qualityTextView.setVisibility(View.GONE); + binding.playbackSpeed.setVisibility(View.GONE); + + binding.playbackEndTime.setVisibility(View.GONE); + binding.playbackLiveSync.setVisibility(View.GONE); + + switch (info.getStreamType()) { + case AUDIO_STREAM: + case POST_LIVE_AUDIO_STREAM: + binding.surfaceView.setVisibility(View.GONE); + binding.endScreen.setVisibility(View.VISIBLE); + binding.playbackEndTime.setVisibility(View.VISIBLE); + break; + + case AUDIO_LIVE_STREAM: + binding.surfaceView.setVisibility(View.GONE); + binding.endScreen.setVisibility(View.VISIBLE); + binding.playbackLiveSync.setVisibility(View.VISIBLE); + break; + + case LIVE_STREAM: + binding.surfaceView.setVisibility(View.VISIBLE); + binding.endScreen.setVisibility(View.GONE); + binding.playbackLiveSync.setVisibility(View.VISIBLE); + break; + + case VIDEO_STREAM: + case POST_LIVE_STREAM: + //noinspection SimplifyOptionalCallChains + if (player.getCurrentMetadata() != null + && !player.getCurrentMetadata().getMaybeQuality().isPresent() + || (info.getVideoStreams().isEmpty() + && info.getVideoOnlyStreams().isEmpty())) { + break; + } + + buildQualityMenu(); + + binding.qualityTextView.setVisibility(View.VISIBLE); + binding.surfaceView.setVisibility(View.VISIBLE); + default: + binding.endScreen.setVisibility(View.GONE); + binding.playbackEndTime.setVisibility(View.VISIBLE); + break; + } + + buildPlaybackSpeedMenu(); + binding.playbackSpeed.setVisibility(View.VISIBLE); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Popup menus ("popup" means that they pop up, not that they belong to the popup player) + //////////////////////////////////////////////////////////////////////////*/ + //region Popup menus ("popup" means that they pop up, not that they belong to the popup player) + private void buildQualityMenu() { + if (qualityPopupMenu == null) { + return; + } + qualityPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_QUALITY); + + @Nullable final List availableStreams + = Optional.ofNullable(player.getCurrentMetadata()) + .flatMap(MediaItemTag::getMaybeQuality) + .map(MediaItemTag.Quality::getSortedVideoStreams) + .orElse(null); + if (availableStreams == null) { + return; + } + + for (int i = 0; i < availableStreams.size(); i++) { + final VideoStream videoStream = availableStreams.get(i); + qualityPopupMenu.getMenu().add(POPUP_MENU_ID_QUALITY, i, Menu.NONE, MediaFormat + .getNameById(videoStream.getFormatId()) + " " + videoStream.getResolution()); + } + final VideoStream selectedVideoStream = player.getSelectedVideoStream(); + if (selectedVideoStream != null) { + binding.qualityTextView.setText(selectedVideoStream.getResolution()); + } + qualityPopupMenu.setOnMenuItemClickListener(this); + qualityPopupMenu.setOnDismissListener(this); + } + + private void buildPlaybackSpeedMenu() { + if (playbackSpeedPopupMenu == null) { + return; + } + playbackSpeedPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_PLAYBACK_SPEED); + + for (int i = 0; i < PLAYBACK_SPEEDS.length; i++) { + playbackSpeedPopupMenu.getMenu().add(POPUP_MENU_ID_PLAYBACK_SPEED, i, Menu.NONE, + formatSpeed(PLAYBACK_SPEEDS[i])); + } + binding.playbackSpeed.setText(formatSpeed(player.getPlaybackSpeed())); + playbackSpeedPopupMenu.setOnMenuItemClickListener(this); + playbackSpeedPopupMenu.setOnDismissListener(this); + } + + private void buildCaptionMenu(@NonNull final List availableLanguages) { + if (captionPopupMenu == null) { + return; + } + captionPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_CAPTION); + + captionPopupMenu.setOnDismissListener(this); + + // Add option for turning off caption + final MenuItem captionOffItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, + 0, Menu.NONE, R.string.caption_none); + captionOffItem.setOnMenuItemClickListener(menuItem -> { + final int textRendererIndex = player.getCaptionRendererIndex(); + if (textRendererIndex != RENDERER_UNAVAILABLE) { + player.getTrackSelector().setParameters(player.getTrackSelector() + .buildUponParameters().setRendererDisabled(textRendererIndex, true)); + } + player.getPrefs().edit() + .remove(context.getString(R.string.caption_user_set_key)).apply(); + return true; + }); + + // Add all available captions + for (int i = 0; i < availableLanguages.size(); i++) { + final String captionLanguage = availableLanguages.get(i); + final MenuItem captionItem = captionPopupMenu.getMenu().add(POPUP_MENU_ID_CAPTION, + i + 1, Menu.NONE, captionLanguage); + captionItem.setOnMenuItemClickListener(menuItem -> { + final int textRendererIndex = player.getCaptionRendererIndex(); + if (textRendererIndex != RENDERER_UNAVAILABLE) { + // DefaultTrackSelector will select for text tracks in the following order. + // When multiple tracks share the same rank, a random track will be chosen. + // 1. ANY track exactly matching preferred language name + // 2. ANY track exactly matching preferred language stem + // 3. ROLE_FLAG_CAPTION track matching preferred language stem + // 4. ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND track matching preferred language stem + // This means if a caption track of preferred language is not available, + // then an auto-generated track of that language will be chosen automatically. + player.getTrackSelector().setParameters(player.getTrackSelector() + .buildUponParameters() + .setPreferredTextLanguages(captionLanguage, + PlayerHelper.captionLanguageStemOf(captionLanguage)) + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) + .setRendererDisabled(textRendererIndex, false)); + player.getPrefs().edit().putString(context.getString( + R.string.caption_user_set_key), captionLanguage).apply(); + } + return true; + }); + } + captionPopupMenu.setOnDismissListener(this); + + // apply caption language from previous user preference + final int textRendererIndex = player.getCaptionRendererIndex(); + if (textRendererIndex == RENDERER_UNAVAILABLE) { + return; + } + + // If user prefers to show no caption, then disable the renderer. + // Otherwise, DefaultTrackSelector may automatically find an available caption + // and display that. + final String userPreferredLanguage = + player.getPrefs().getString(context.getString(R.string.caption_user_set_key), null); + if (userPreferredLanguage == null) { + player.getTrackSelector().setParameters(player.getTrackSelector().buildUponParameters() + .setRendererDisabled(textRendererIndex, true)); + return; + } + + // Only set preferred language if it does not match the user preference, + // otherwise there might be an infinite cycle at onTextTracksChanged. + final List selectedPreferredLanguages = + player.getTrackSelector().getParameters().preferredTextLanguages; + if (!selectedPreferredLanguages.contains(userPreferredLanguage)) { + player.getTrackSelector().setParameters(player.getTrackSelector().buildUponParameters() + .setPreferredTextLanguages(userPreferredLanguage, + PlayerHelper.captionLanguageStemOf(userPreferredLanguage)) + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION) + .setRendererDisabled(textRendererIndex, false)); + } + } + + protected abstract void onPlaybackSpeedClicked(); + + private void onQualityClicked() { + qualityPopupMenu.show(); + isSomePopupMenuVisible = true; + + final VideoStream videoStream = player.getSelectedVideoStream(); + if (videoStream != null) { + //noinspection SetTextI18n + binding.qualityTextView.setText(MediaFormat.getNameById(videoStream.getFormatId()) + + " " + videoStream.getResolution()); + } + + player.saveWasPlaying(); + } + + /** + * Called when an item of the quality selector or the playback speed selector is selected. + */ + @Override + public boolean onMenuItemClick(@NonNull final MenuItem menuItem) { + if (DEBUG) { + Log.d(TAG, "onMenuItemClick() called with: " + + "menuItem = [" + menuItem + "], " + + "menuItem.getItemId = [" + menuItem.getItemId() + "]"); + } + + if (menuItem.getGroupId() == POPUP_MENU_ID_QUALITY) { + final int menuItemIndex = menuItem.getItemId(); + @Nullable final MediaItemTag currentMetadata = player.getCurrentMetadata(); + //noinspection SimplifyOptionalCallChains + if (currentMetadata == null || !currentMetadata.getMaybeQuality().isPresent()) { + return true; + } + + final MediaItemTag.Quality quality = currentMetadata.getMaybeQuality().get(); + final List availableStreams = quality.getSortedVideoStreams(); + final int selectedStreamIndex = quality.getSelectedVideoStreamIndex(); + if (selectedStreamIndex == menuItemIndex|| availableStreams.size() <= menuItemIndex) { + return true; + } + + player.saveStreamProgressState(); //TODO added, check if good + final String newResolution = availableStreams.get(menuItemIndex).getResolution(); + player.setRecovery(); + player.setPlaybackQuality(newResolution); + player.reloadPlayQueueManager(); + + binding.qualityTextView.setText(menuItem.getTitle()); + return true; + } else if (menuItem.getGroupId() == POPUP_MENU_ID_PLAYBACK_SPEED) { + final int speedIndex = menuItem.getItemId(); + final float speed = PLAYBACK_SPEEDS[speedIndex]; + + player.setPlaybackSpeed(speed); + binding.playbackSpeed.setText(formatSpeed(speed)); + } + + return false; + } + + /** + * Called when some popup menu is dismissed. + */ + @Override + public void onDismiss(@Nullable final PopupMenu menu) { + if (DEBUG) { + Log.d(TAG, "onDismiss() called with: menu = [" + menu + "]"); + } + isSomePopupMenuVisible = false; //TODO check if this works + final VideoStream selectedVideoStream = player.getSelectedVideoStream(); + if (selectedVideoStream != null) { + binding.qualityTextView.setText(selectedVideoStream.getResolution()); + } + if (player.isPlaying()) { + hideControls(DEFAULT_CONTROLS_DURATION, 0); + hideSystemUIIfNeeded(); + } + } + + private void onCaptionClicked() { + if (DEBUG) { + Log.d(TAG, "onCaptionClicked() called"); + } + captionPopupMenu.show(); + isSomePopupMenuVisible = true; + } + + public boolean isSomePopupMenuVisible() { + return isSomePopupMenuVisible; + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Captions (text tracks) + //////////////////////////////////////////////////////////////////////////*/ + //region Captions (text tracks) + private void setupSubtitleView() { + setupSubtitleView(PlayerHelper.getCaptionScale(context)); + final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context); + binding.subtitleView.setApplyEmbeddedStyles(captionStyle == CaptionStyleCompat.DEFAULT); + binding.subtitleView.setStyle(captionStyle); + } + + protected abstract void setupSubtitleView(final float captionScale); + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Click listeners + //////////////////////////////////////////////////////////////////////////*/ + //region Click listeners + @Override + public void onClick(final View v) { + if (DEBUG) { + Log.d(TAG, "onClick() called with: v = [" + v + "]"); + } + if (v.getId() == binding.resizeTextView.getId()) { + onResizeClicked(); + } else if (v.getId() == binding.captionTextView.getId()) { + onCaptionClicked(); + } else if (v.getId() == binding.playbackLiveSync.getId()) { + player.seekToDefault(); + } else if (v.getId() == binding.playPauseButton.getId()) { + player.playPause(); + } else if (v.getId() == binding.playPreviousButton.getId()) { + player.playPrevious(); + } else if (v.getId() == binding.playNextButton.getId()) { + player.playNext(); + } else if (v.getId() == binding.moreOptionsButton.getId()) { + onMoreOptionsClicked(); + } else if (v.getId() == binding.share.getId()) { + final PlayQueueItem currentItem = player.getCurrentItem(); + if (currentItem != null) { + ShareUtils.shareText(context, currentItem.getTitle(), + player.getVideoUrlAtCurrentTime(), currentItem.getThumbnailUrl()); + } + } else if (v.getId() == binding.playWithKodi.getId()) { + onPlayWithKodiClicked(); + } else if (v.getId() == binding.openInBrowser.getId()) { + onOpenInBrowserClicked(); + } else if (v.getId() == binding.fullScreenButton.getId()) { + player.setRecovery(); + NavigationHelper.playOnMainPlayer(context, player.getPlayQueue(), true); + return; + } else if (v.getId() == binding.switchMute.getId()) { + player.toggleMute(); + } else if (v.getId() == binding.playerCloseButton.getId()) { + context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)); + } else if (v.getId() == binding.playbackSpeed.getId()) { + onPlaybackSpeedClicked(); + } else if (v.getId() == binding.qualityTextView.getId()) { + onQualityClicked(); + } + + manageControlsAfterOnClick(v); + } + + /** + * Manages the controls after a click occurred on the player UI. + * @param v – The view that was clicked + */ + public void manageControlsAfterOnClick(@NonNull final View v) { + if (player.getCurrentState() == STATE_COMPLETED) { + return; + } + + controlsVisibilityHandler.removeCallbacksAndMessages(null); + showHideShadow(true, DEFAULT_CONTROLS_DURATION); + animate(binding.playbackControlRoot, true, DEFAULT_CONTROLS_DURATION, + AnimationType.ALPHA, 0, () -> { + if (player.getCurrentState() == STATE_PLAYING && !isSomePopupMenuVisible) { + if (v.getId() == binding.playPauseButton.getId() + // Hide controls in fullscreen immediately + || (v.getId() == binding.screenRotationButton.getId() + && isFullscreen())) { + hideControls(0, 0); + } else { + hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); + } + } + }); + } + + @Override + public boolean onLongClick(final View v) { + if (v.getId() == binding.share.getId()) { + ShareUtils.copyToClipboard(context, player.getVideoUrlAtCurrentTime()); + } + return true; + } + + public boolean onKeyDown(final int keyCode) { + switch (keyCode) { + default: + break; + case KeyEvent.KEYCODE_BACK: + if (DeviceUtils.isTv(context) && isControlsVisible()) { + hideControls(0, 0); + return true; + } + break; + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_RIGHT: + case KeyEvent.KEYCODE_DPAD_CENTER: + if ((binding.getRoot().hasFocus() && !binding.playbackControlRoot.hasFocus()) + || isAnyListViewOpen()) { + // do not interfere with focus in playlist and play queue etc. + return false; + } + + if (player.getCurrentState() == org.schabi.newpipe.player.Player.STATE_BLOCKED) { + return true; + } + + if (isControlsVisible()) { + hideControls(DEFAULT_CONTROLS_DURATION, DPAD_CONTROLS_HIDE_TIME); + } else { + binding.playPauseButton.requestFocus(); + showControlsThenHide(); + showSystemUIPartially(); + return true; + } + break; + } + + return false; + } + + private void onMoreOptionsClicked() { + if (DEBUG) { + Log.d(TAG, "onMoreOptionsClicked() called"); + } + + final boolean isMoreControlsVisible = + binding.secondaryControls.getVisibility() == View.VISIBLE; + + animateRotation(binding.moreOptionsButton, DEFAULT_CONTROLS_DURATION, + isMoreControlsVisible ? 0 : 180); + animate(binding.secondaryControls, !isMoreControlsVisible, DEFAULT_CONTROLS_DURATION, + AnimationType.SLIDE_AND_ALPHA, 0, () -> { + // Fix for a ripple effect on background drawable. + // When view returns from GONE state it takes more milliseconds than returning + // from INVISIBLE state. And the delay makes ripple background end to fast + if (isMoreControlsVisible) { + binding.secondaryControls.setVisibility(View.INVISIBLE); + } + }); + showControls(DEFAULT_CONTROLS_DURATION); + } + + private void onPlayWithKodiClicked() { + if (player.getCurrentMetadata() != null) { + player.pause(); + try { + NavigationHelper.playWithKore(context, Uri.parse(player.getVideoUrl())); + } catch (final Exception e) { + if (DEBUG) { + Log.i(TAG, "Failed to start kore", e); + } + KoreUtils.showInstallKoreDialog(player.getContext()); + } + } + } + + private void onOpenInBrowserClicked() { + player.getCurrentStreamInfo().ifPresent(streamInfo -> + ShareUtils.openUrlInBrowser(player.getContext(), streamInfo.getOriginalUrl())); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Video size, resize, orientation, fullscreen + //////////////////////////////////////////////////////////////////////////*/ + //region Video size, resize, orientation, fullscreen + protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { + binding.surfaceView.setResizeMode(resizeMode); + binding.resizeTextView.setText(PlayerHelper.resizeTypeOf(context, resizeMode)); + } + + void onResizeClicked() { + setResizeMode(nextResizeModeAndSaveToPrefs(player, binding.surfaceView.getResizeMode())); + } + + @Override + public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + super.onVideoSizeChanged(videoSize); + binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height); + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // SurfaceHolderCallback helpers + //////////////////////////////////////////////////////////////////////////*/ + //region SurfaceHolderCallback helpers + private void setupVideoSurface() { + // make sure there is nothing left over from previous calls + cleanupVideoSurface(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer()); + binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); + final Surface surface = binding.surfaceView.getHolder().getSurface(); + + // ensure player is using an unreleased surface, which the surfaceView might not be + // when starting playback on background or during player switching + if (surface.isValid()) { + // initially set the surface manually otherwise + // onRenderedFirstFrame() will not be called + player.getExoPlayer().setVideoSurface(surface); + } + + } else { + player.getExoPlayer().setVideoSurfaceView(binding.surfaceView); + } + } + + private void cleanupVideoSurface() { + final Optional exoPlayer = Optional.ofNullable(player.getExoPlayer()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + if (surfaceHolderCallback != null) { + binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); + surfaceHolderCallback.release(); + surfaceHolderCallback = null; + } + exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurface(null)); + } else { + exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurfaceView(null)); + } + } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Getters + //////////////////////////////////////////////////////////////////////////*/ + //region Getters + public PlayerBinding getBinding() { + return binding; + } + + public GestureDetector getGestureDetector() { + return gestureDetector; + } + //endregion +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 849574171..0eb58f7a9 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -26,7 +26,7 @@ import androidx.preference.PreferenceViewHolder; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; -import org.schabi.newpipe.player.MainPlayer; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.NotificationConstants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; @@ -61,7 +61,7 @@ public class NotificationActionsPreference extends Preference { public void onDetached() { super.onDetached(); saveChanges(); - getContext().sendBroadcast(new Intent(MainPlayer.ACTION_RECREATE_NOTIFICATION)); + getContext().sendBroadcast(new Intent(PlayerService.ACTION_RECREATE_NOTIFICATION)); } 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 c40b1a430..36b2bd46d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -50,8 +50,8 @@ import org.schabi.newpipe.local.history.StatisticsPlaylistFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment; -import org.schabi.newpipe.player.MainPlayer; -import org.schabi.newpipe.player.MainPlayer.PlayerType; +import org.schabi.newpipe.player.PlayerService; +import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.PlayQueueActivity; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.helper.PlayerHelper; @@ -91,7 +91,7 @@ public final class NavigationHelper { intent.putExtra(Player.PLAY_QUEUE_KEY, cacheKey); } } - intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.VIDEO.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal()); intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback); return intent; @@ -163,8 +163,8 @@ public final class NavigationHelper { Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); - final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.POPUP.ordinal()); + final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); + intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.POPUP.ordinal()); ContextCompat.startForegroundService(context, intent); } @@ -174,8 +174,8 @@ public final class NavigationHelper { Toast.makeText(context, R.string.background_player_playing_toast, Toast.LENGTH_SHORT) .show(); - final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, MainPlayer.PlayerType.AUDIO.ordinal()); + final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); + intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.AUDIO.ordinal()); ContextCompat.startForegroundService(context, intent); } @@ -184,7 +184,7 @@ public final class NavigationHelper { final PlayQueue queue, final PlayerType playerType) { Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show(); - final Intent intent = getPlayerEnqueueIntent(context, MainPlayer.class, queue); + final Intent intent = getPlayerEnqueueIntent(context, PlayerService.class, queue); intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); ContextCompat.startForegroundService(context, intent); @@ -194,7 +194,7 @@ public final class NavigationHelper { PlayerType playerType = PlayerHolder.getInstance().getType(); if (!PlayerHolder.getInstance().isPlayerOpen()) { Log.e(TAG, "Enqueueing but no player is open; defaulting to background player"); - playerType = MainPlayer.PlayerType.AUDIO; + playerType = PlayerService.PlayerType.AUDIO; } enqueueOnPlayer(context, queue, playerType); @@ -205,10 +205,10 @@ public final class NavigationHelper { PlayerType playerType = PlayerHolder.getInstance().getType(); if (!PlayerHolder.getInstance().isPlayerOpen()) { Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player"); - playerType = MainPlayer.PlayerType.AUDIO; + playerType = PlayerService.PlayerType.AUDIO; } Toast.makeText(context, R.string.enqueued_next, Toast.LENGTH_SHORT).show(); - final Intent intent = getPlayerEnqueueNextIntent(context, MainPlayer.class, queue); + final Intent intent = getPlayerEnqueueNextIntent(context, PlayerService.class, queue); intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); ContextCompat.startForegroundService(context, intent); @@ -414,14 +414,14 @@ public final class NavigationHelper { final boolean switchingPlayers) { final boolean autoPlay; - @Nullable final MainPlayer.PlayerType playerType = PlayerHolder.getInstance().getType(); + @Nullable final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); if (!PlayerHolder.getInstance().isPlayerOpen()) { // no player open autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else if (switchingPlayers) { // switching player to main player autoPlay = PlayerHolder.getInstance().isPlaying(); // keep play/pause state - } else if (playerType == MainPlayer.PlayerType.VIDEO) { + } else if (playerType == PlayerService.PlayerType.MAIN) { // opening new stream while already playing in main player autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else { @@ -436,7 +436,7 @@ public final class NavigationHelper { // Situation when user switches from players to main player. All needed data is // here, we can start watching (assuming newQueue equals playQueue). // Starting directly in fullscreen if the previous player type was popup. - detailFragment.openVideoPlayer(playerType == MainPlayer.PlayerType.POPUP + detailFragment.openVideoPlayer(playerType == PlayerService.PlayerType.POPUP || PlayerHelper.isStartMainPlayerFullscreenEnabled(context)); } else { detailFragment.selectAndLoadVideo(serviceId, url, title, playQueue); diff --git a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt index 649b60494..cbba0a75b 100644 --- a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt +++ b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt @@ -12,8 +12,8 @@ import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.START import androidx.constraintlayout.widget.ConstraintSet import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R -import org.schabi.newpipe.player.event.DisplayPortion -import org.schabi.newpipe.player.event.DoubleTapListener +import org.schabi.newpipe.player.gesture.DisplayPortion +import org.schabi.newpipe.player.gesture.DoubleTapListener class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs), DoubleTapListener { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 97ccd199e..01d842812 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -25,7 +25,7 @@ android:layout_gravity="center_horizontal" app:behavior_hideable="true" app:behavior_peekHeight="0dp" - app:layout_behavior="org.schabi.newpipe.player.event.CustomBottomSheetBehavior" /> + app:layout_behavior="org.schabi.newpipe.player.gesture.CustomBottomSheetBehavior" /> From b3f99645a39005ddfad19f61b98b272c81414470 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Apr 2022 10:48:34 +0200 Subject: [PATCH 187/992] Fix some crashes / issues after player refactor --- .../fragments/detail/VideoDetailFragment.java | 28 ++-- .../org/schabi/newpipe/player/Player.java | 8 +- .../gesture/MainPlayerGestureListener.kt | 15 +-- .../newpipe/player/ui/MainPlayerUi.java | 95 +++++++++----- .../schabi/newpipe/player/ui/PlayerUi.java | 1 - .../newpipe/player/ui/PopupPlayerUi.java | 35 +++-- .../newpipe/player/ui/VideoPlayerUi.java | 123 +++++++++++++----- .../views/player/PlayerFastSeekOverlay.kt | 6 +- 8 files changed, 192 insertions(+), 119 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 5ecc35034..cb8f0961f 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 @@ -240,10 +240,6 @@ public final class VideoDetailFragment playerUi.ifPresent(MainPlayerUi::toggleFullscreen); } - if (playerIsNotStopped() && player.videoPlayerSelected()) { - addVideoPlayerView(); - } - //noinspection SimplifyOptionalCallChains if (playAfterConnect || (currentInfo != null @@ -335,6 +331,9 @@ public final class VideoDetailFragment @Override public void onResume() { super.onResume(); + if (DEBUG) { + Log.d(TAG, "onResume() called"); + } activity.sendBroadcast(new Intent(ACTION_VIDEO_FRAGMENT_RESUMED)); @@ -1310,22 +1309,14 @@ public final class VideoDetailFragment if (!isPlayerAvailable()) { return; } - - final Optional root = player.UIs().get(VideoPlayerUi.class) - .map(VideoPlayerUi::getBinding) - .map(ViewBinding::getRoot); - - // Check if viewHolder already contains a child TODO TODO whaat - /*if (playerService != null - && root.map(View::getParent).orElse(null) != binding.playerPlaceholder) { - playerService.removeViewFromParent(); - }*/ setHeightThumbnail(); // Prevent from re-adding a view multiple times - if (root.isPresent() && root.get().getParent() == null) { - binding.playerPlaceholder.addView(root.get()); - } + new Handler().post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + })); } private void removeVideoPlayerView() { @@ -1793,9 +1784,6 @@ public final class VideoDetailFragment @Override public void onViewCreated() { - // Video view can have elements visible from popup, - // We hide it here but once it ready the view will be shown in handleIntent() - getRoot().ifPresent(view -> view.setVisibility(View.GONE)); addVideoPlayerView(); } 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 284ab74d8..78e93970c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -485,6 +485,10 @@ public final class Player implements PlaybackListener, Listener { // make sure UIs know whether a service is connected or not UIs.call(PlayerUi::onFragmentListenerSet); } + if (!exoPlayerIsNull()) { + UIs.call(PlayerUi::initPlayer); + UIs.call(PlayerUi::initPlayback); + } } private void initPlayback(@NonNull final PlayQueue queue, @@ -599,7 +603,7 @@ public final class Player implements PlaybackListener, Listener { progressUpdateDisposable.set(null); PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading - UIs.call(PlayerUi::destroy); + UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object } public void setRecovery() { @@ -737,7 +741,7 @@ public final class Player implements PlaybackListener, Listener { case Intent.ACTION_CONFIGURATION_CHANGED: assureCorrectAppLanguage(service); if (DEBUG) { - Log.d(TAG, "onConfigurationChanged() called"); + Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received"); } break; case Intent.ACTION_HEADSET_PLUG: //FIXME diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index 17205fb9a..81e216006 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -1,12 +1,12 @@ package org.schabi.newpipe.player.gesture -import android.app.Activity import android.content.Context import android.util.Log import android.view.MotionEvent import android.view.View import android.view.View.OnTouchListener import android.widget.ProgressBar +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R @@ -29,8 +29,6 @@ import kotlin.math.min class MainPlayerGestureListener( private val playerUi: MainPlayerUi ) : BasePlayerGestureListener(playerUi), OnTouchListener { - private val maxVolume: Int = player.audioReactor.maxVolume - private var isMoving = false override fun onTouch(v: View, event: MotionEvent): Boolean { @@ -41,11 +39,11 @@ class MainPlayerGestureListener( } return when (event.action) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { - v.parent.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) + v.parent?.requestDisallowInterceptTouchEvent(playerUi.isFullscreen) true } MotionEvent.ACTION_UP -> { - v.parent.requestDisallowInterceptTouchEvent(false) + v.parent?.requestDisallowInterceptTouchEvent(false) false } else -> true @@ -68,14 +66,15 @@ class MainPlayerGestureListener( private fun onScrollVolume(distanceY: Float) { // If we just started sliding, change the progress bar to match the system volume if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { - val volumePercent: Float = player.audioReactor.volume / maxVolume.toFloat() + val volumePercent: Float = + player.audioReactor.volume / player.audioReactor.maxVolume.toFloat() binding.volumeProgressBar.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt() } binding.volumeProgressBar.incrementProgressBy(distanceY.toInt()) val currentProgressPercent: Float = binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH - val currentVolume = (maxVolume * currentProgressPercent).toInt() + val currentVolume = (player.audioReactor.maxVolume * currentProgressPercent).toInt() player.audioReactor.volume = currentVolume if (DEBUG) { Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume") @@ -102,7 +101,7 @@ class MainPlayerGestureListener( } private fun onScrollBrightness(distanceY: Float) { - val parent: Activity = playerUi.parentActivity + val parent: AppCompatActivity = playerUi.parentActivity.orElse(null) ?: return val window = parent.window val layoutParams = window.attributes val bar: ProgressBar = binding.brightnessProgressBar diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 10ed424ba..7c60671dd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -13,12 +13,13 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAct import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; +import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Color; -import android.os.Build; import android.os.Handler; import android.provider.Settings; import android.util.DisplayMetrics; @@ -28,7 +29,6 @@ import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; -import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.LinearLayout; @@ -37,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.content.res.AppCompatResources; +import androidx.fragment.app.FragmentActivity; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; @@ -68,8 +69,9 @@ import org.schabi.newpipe.util.external_communication.KoreUtils; import java.util.List; import java.util.Objects; +import java.util.Optional; -public final class MainPlayerUi extends VideoPlayerUi { +public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutChangeListener { private static final String TAG = MainPlayerUi.class.getSimpleName(); private boolean isFullscreen = false; @@ -113,7 +115,6 @@ public final class MainPlayerUi extends VideoPlayerUi { super.setupAfterIntent(); - binding.getRoot().setVisibility(View.VISIBLE); initVideoPlayer(); // Android TV: without it focus will frame the whole player binding.playPauseButton.requestFocus(); @@ -139,7 +140,8 @@ public final class MainPlayerUi extends VideoPlayerUi { binding.segmentsButton.setOnClickListener(v -> onSegmentsClicked()); binding.addToPlaylistButton.setOnClickListener(v -> - player.onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager())); + getParentActivity().map(FragmentActivity::getSupportFragmentManager) + .ifPresent(player::onAddToPlaylistClicked)); settingsContentObserver = new ContentObserver(new Handler()) { @Override @@ -151,7 +153,20 @@ public final class MainPlayerUi extends VideoPlayerUi { Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, settingsContentObserver); - binding.getRoot().addOnLayoutChangeListener(this::onLayoutChange); + binding.getRoot().addOnLayoutChangeListener(this); + } + + @Override + protected void deinitListeners() { + super.deinitListeners(); + + binding.queueButton.setOnClickListener(null); + binding.segmentsButton.setOnClickListener(null); + binding.addToPlaylistButton.setOnClickListener(null); + + context.getContentResolver().unregisterContentObserver(settingsContentObserver); + + binding.getRoot().removeOnLayoutChangeListener(this); } @Override @@ -178,7 +193,6 @@ public final class MainPlayerUi extends VideoPlayerUi { @Override public void destroy() { super.destroy(); - context.getContentResolver().unregisterContentObserver(settingsContentObserver); // Exit from fullscreen when user closes the player via notification if (isFullscreen) { @@ -324,9 +338,10 @@ public final class MainPlayerUi extends VideoPlayerUi { player.useVideoSource(false); break; case MINIMIZE_ON_EXIT_MODE_POPUP: - player.setRecovery(); - NavigationHelper.playOnPopupPlayer(getParentActivity(), - player.getPlayQueue(), true); + getParentActivity().ifPresent(activity -> { + player.setRecovery(); + NavigationHelper.playOnPopupPlayer(activity, player.getPlayQueue(), true); + }); break; case MINIMIZE_ON_EXIT_MODE_NONE: default: player.pause(); @@ -385,14 +400,15 @@ public final class MainPlayerUi extends VideoPlayerUi { @Override public void showSystemUIPartially() { if (isFullscreen) { - final Window window = getParentActivity().getWindow(); - window.setStatusBarColor(Color.TRANSPARENT); - window.setNavigationBarColor(Color.TRANSPARENT); - final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - window.getDecorView().setSystemUiVisibility(visibility); - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getParentActivity().map(Activity::getWindow).ifPresent(window -> { + window.setStatusBarColor(Color.TRANSPARENT); + window.setNavigationBarColor(Color.TRANSPARENT); + final int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + window.getDecorView().setSystemUiVisibility(visibility); + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + }); } } @@ -476,8 +492,9 @@ public final class MainPlayerUi extends VideoPlayerUi { //region Gestures @SuppressWarnings("checkstyle:ParameterNumber") - private void onLayoutChange(final View view, final int l, final int t, final int r, final int b, - final int ol, final int ot, final int or, final int ob) { + @Override + public void onLayoutChange(final View view, final int l, final int t, final int r, final int b, + final int ol, final int ot, final int or, final int ob) { if (l != ol || t != ot || r != or || b != ob) { // Use smaller value to be consistent between screen orientations // (and to make usage easier) @@ -501,9 +518,8 @@ public final class MainPlayerUi extends VideoPlayerUi { private void setInitialGestureValues() { if (player.getAudioReactor() != null) { - final float currentVolumeNormalized = - (float) player.getAudioReactor().getVolume() - / player.getAudioReactor().getMaxVolume(); + final float currentVolumeNormalized = (float) player.getAudioReactor().getVolume() + / player.getAudioReactor().getMaxVolume(); binding.volumeProgressBar.setProgress( (int) (binding.volumeProgressBar.getMax() * currentVolumeNormalized)); } @@ -714,7 +730,7 @@ public final class MainPlayerUi extends VideoPlayerUi { @Override public void held(final PlayQueueItem item, final View view) { @Nullable final PlayQueue playQueue = player.getPlayQueue(); - @Nullable final AppCompatActivity parentActivity = getParentActivity(); + @Nullable final AppCompatActivity parentActivity = getParentActivity().orElse(null); if (playQueue != null && parentActivity != null && playQueue.indexOf(item) != -1) { openPopupMenu(player.getPlayQueue(), item, view, true, parentActivity.getSupportFragmentManager(), context); @@ -801,10 +817,15 @@ public final class MainPlayerUi extends VideoPlayerUi { @Override protected void onPlaybackSpeedClicked() { + final AppCompatActivity activity = getParentActivity().orElse(null); + if (activity == null) { + return; + } + PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), player.getPlaybackSkipSilence(), (speed, pitch, skipSilence) -> player.setPlaybackParameters(speed, pitch, skipSilence)) - .show(getParentActivity().getSupportFragmentManager(), null); + .show(activity.getSupportFragmentManager(), null); } @Override @@ -876,15 +897,15 @@ public final class MainPlayerUi extends VideoPlayerUi { } isFullscreen = !isFullscreen; - if (!isFullscreen) { - // Apply window insets because Android will not do it when orientation changes - // from landscape to portrait (open vertical video to reproduce) - binding.playbackControlRoot.setPadding(0, 0, 0, 0); - } else { + if (isFullscreen) { // Android needs tens milliseconds to send new insets but a user is able to see // how controls changes it's position from `0` to `nav bar height` padding. // So just hide the controls to hide this visual inconsistency hideControls(0, 0); + } else { + // Apply window insets because Android will not do it when orientation changes + // from landscape to portrait (open vertical video to reproduce) + binding.playbackControlRoot.setPadding(0, 0, 0, 0); } fragmentListener.onFullscreenStateChanged(isFullscreen); @@ -924,14 +945,22 @@ public final class MainPlayerUi extends VideoPlayerUi { return binding; } - public AppCompatActivity getParentActivity() { - return (AppCompatActivity) ((ViewGroup) binding.getRoot().getParent()).getContext(); + public Optional getParentActivity() { + final ViewParent rootParent = binding.getRoot().getParent(); + if (rootParent instanceof ViewGroup) { + final Context activity = ((ViewGroup) rootParent).getContext(); + if (activity instanceof AppCompatActivity) { + return Optional.of((AppCompatActivity) activity); + } + } + return Optional.empty(); } public boolean isLandscape() { // DisplayMetrics from activity context knows about MultiWindow feature // while DisplayMetrics from app context doesn't - return DeviceUtils.isLandscape(getParentActivity()); + return DeviceUtils.isLandscape( + getParentActivity().map(Context.class::cast).orElse(player.getService())); } //endregion } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index fd63790d6..15b468fb7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -19,7 +19,6 @@ import org.schabi.newpipe.player.Player; import java.util.List; public abstract class PlayerUi { - private static final String TAG = PlayerUi.class.getSimpleName(); @NonNull protected Context context; @NonNull protected Player player; 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 b8a26a233..7df9102b7 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 @@ -69,11 +69,9 @@ public final class PopupPlayerUi extends VideoPlayerUi { @Override public void setupAfterIntent() { - setupElementsVisibility(); - binding.getRoot().setVisibility(View.VISIBLE); + super.setupAfterIntent(); initPopup(); initPopupCloseOverlay(); - binding.playPauseButton.requestFocus(); } @Override @@ -103,6 +101,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { binding.loadingPanel.setMinimumHeight(popupLayoutParams.height); windowManager.addView(binding.getRoot(), popupLayoutParams); + setupVideoSurfaceIfNeeded(); // now there is a parent, we can setup video surface // Popup doesn't have aspectRatio selector, using FIT automatically setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); @@ -304,25 +303,23 @@ public final class PopupPlayerUi extends VideoPlayerUi { } public void removePopupFromView() { - if (windowManager != null) { - // wrap in try-catch since it could sometimes generate errors randomly - try { - if (popupHasParent()) { - windowManager.removeView(binding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup from window manager", e); + // wrap in try-catch since it could sometimes generate errors randomly + try { + if (popupHasParent()) { + windowManager.removeView(binding.getRoot()); } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup from window manager", e); + } - try { - final boolean closeOverlayHasParent = closeOverlayBinding != null - && closeOverlayBinding.getRoot().getParent() != null; - if (closeOverlayHasParent) { - windowManager.removeView(closeOverlayBinding.getRoot()); - } - } catch (final IllegalArgumentException e) { - Log.w(TAG, "Failed to remove popup overlay from window manager", e); + try { + final boolean closeOverlayHasParent = closeOverlayBinding != null + && closeOverlayBinding.getRoot().getParent() != null; + if (closeOverlayHasParent) { + windowManager.removeView(closeOverlayBinding.getRoot()); } + } catch (final IllegalArgumentException e) { + Log.w(TAG, "Failed to remove popup overlay from window manager", e); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 99ecb5540..24cdb8908 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -32,7 +32,6 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; -import android.view.Surface; import android.view.View; import android.widget.LinearLayout; import android.widget.RelativeLayout; @@ -107,6 +106,7 @@ public abstract class VideoPlayerUi extends PlayerUi protected PlayerBinding binding; private final Handler controlsVisibilityHandler = new Handler(); @Nullable private SurfaceHolderCallback surfaceHolderCallback; + boolean surfaceIsSetup = false; @Nullable private Bitmap thumbnail = null; @@ -130,6 +130,7 @@ public abstract class VideoPlayerUi extends PlayerUi private GestureDetector gestureDetector; private BasePlayerGestureListener playerGestureListener; + @Nullable private View.OnLayoutChangeListener onLayoutChangeListener = null; @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = new SeekbarPreviewThumbnailHolder(); @@ -138,6 +139,7 @@ public abstract class VideoPlayerUi extends PlayerUi @NonNull final PlayerBinding playerBinding) { super(player); binding = playerBinding; + setupFromView(); } @@ -222,8 +224,8 @@ public abstract class VideoPlayerUi extends PlayerUi // PlaybackControlRoot already consumed window insets but we should pass them to // player_overlays and fast_seek_overlay too. Without it they will be off-centered. - binding.playbackControlRoot.addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + onLayoutChangeListener + = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { binding.playerOverlays.setPadding( v.getPaddingLeft(), v.getPaddingTop(), @@ -240,7 +242,43 @@ public abstract class VideoPlayerUi extends PlayerUi fastSeekParams.topMargin = -v.getPaddingBottom(); fastSeekParams.rightMargin = -v.getPaddingLeft(); fastSeekParams.bottomMargin = -v.getPaddingTop(); - }); + }; + binding.playbackControlRoot.addOnLayoutChangeListener(onLayoutChangeListener); + } + + protected void deinitListeners() { + binding.qualityTextView.setOnClickListener(null); + binding.playbackSpeed.setOnClickListener(null); + binding.playbackSeekBar.setOnSeekBarChangeListener(null); + binding.captionTextView.setOnClickListener(null); + binding.resizeTextView.setOnClickListener(null); + binding.playbackLiveSync.setOnClickListener(null); + + binding.getRoot().setOnTouchListener(null); + playerGestureListener = null; + gestureDetector = null; + + binding.repeatButton.setOnClickListener(null); + binding.shuffleButton.setOnClickListener(null); + + binding.playPauseButton.setOnClickListener(null); + binding.playPreviousButton.setOnClickListener(null); + binding.playNextButton.setOnClickListener(null); + + binding.moreOptionsButton.setOnClickListener(null); + binding.moreOptionsButton.setOnLongClickListener(null); + binding.share.setOnClickListener(null); + binding.share.setOnLongClickListener(null); + binding.fullScreenButton.setOnClickListener(null); + binding.screenRotationButton.setOnClickListener(null); + binding.playWithKodi.setOnClickListener(null); + binding.openInBrowser.setOnClickListener(null); + binding.playerCloseButton.setOnClickListener(null); + binding.switchMute.setOnClickListener(null); + + ViewCompat.setOnApplyWindowInsetsListener(binding.itemsListPanel, null); + + binding.playbackControlRoot.removeOnLayoutChangeListener(onLayoutChangeListener); } /** @@ -304,18 +342,25 @@ public abstract class VideoPlayerUi extends PlayerUi playerGestureListener.doubleTapControls(binding.fastSeekOverlay); } + public void deinitPlayerSeekOverlay() { + binding.fastSeekOverlay + .seekSecondsSupplier(null) + .performListener(null); + } + @Override public void setupAfterIntent() { super.setupAfterIntent(); setupElementsVisibility(); setupElementsSize(context.getResources()); + binding.getRoot().setVisibility(View.VISIBLE); + binding.playPauseButton.requestFocus(); } @Override public void initPlayer() { super.initPlayer(); - setupVideoSurface(); - setupFromView(); + setupVideoSurfaceIfNeeded(); } @Override @@ -331,7 +376,7 @@ public abstract class VideoPlayerUi extends PlayerUi @Override public void destroyPlayer() { super.destroyPlayer(); - cleanupVideoSurface(); + clearVideoSurface(); } @Override @@ -340,6 +385,8 @@ public abstract class VideoPlayerUi extends PlayerUi if (binding != null) { binding.endScreen.setImageBitmap(null); } + deinitPlayerSeekOverlay(); + deinitListeners(); } protected void setupElementsVisibility() { @@ -1470,40 +1517,50 @@ public abstract class VideoPlayerUi extends PlayerUi // SurfaceHolderCallback helpers //////////////////////////////////////////////////////////////////////////*/ //region SurfaceHolderCallback helpers - private void setupVideoSurface() { - // make sure there is nothing left over from previous calls - cleanupVideoSurface(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 - surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer()); - binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); - final Surface surface = binding.surfaceView.getHolder().getSurface(); + /** + * Connects the video surface to the exo player. This can be called anytime without the risk for + * issues to occur, since the player will run just fine when no surface is connected. Therefore + * the video surface will be setup only when all of these conditions are true: it is not already + * setup (this just prevents wasting resources to setup the surface again), there is an exo + * player, the root view is attached to a parent and the surface view is valid/unreleased (the + * latter two conditions prevent "The surface has been released" errors). So this function can + * be called many times and even while the UI is in unready states. + */ + public void setupVideoSurfaceIfNeeded() { + if (!surfaceIsSetup && player.getExoPlayer() != null + && binding.getRoot().getParent() != null) { + // make sure there is nothing left over from previous calls + clearVideoSurface(); - // ensure player is using an unreleased surface, which the surfaceView might not be - // when starting playback on background or during player switching - if (surface.isValid()) { - // initially set the surface manually otherwise - // onRenderedFirstFrame() will not be called - player.getExoPlayer().setVideoSurface(surface); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + surfaceHolderCallback = new SurfaceHolderCallback(context, player.getExoPlayer()); + binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); + + // ensure player is using an unreleased surface, which the surfaceView might not be + // when starting playback on background or during player switching + if (binding.surfaceView.getHolder().getSurface().isValid()) { + // initially set the surface manually otherwise + // onRenderedFirstFrame() will not be called + player.getExoPlayer().setVideoSurfaceHolder(binding.surfaceView.getHolder()); + } + } else { + player.getExoPlayer().setVideoSurfaceView(binding.surfaceView); } - } else { - player.getExoPlayer().setVideoSurfaceView(binding.surfaceView); + surfaceIsSetup = true; } } - private void cleanupVideoSurface() { - final Optional exoPlayer = Optional.ofNullable(player.getExoPlayer()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 - if (surfaceHolderCallback != null) { - binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); - surfaceHolderCallback.release(); - surfaceHolderCallback = null; - } - exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurface(null)); - } else { - exoPlayer.ifPresent(simpleExoPlayer -> simpleExoPlayer.setVideoSurfaceView(null)); + private void clearVideoSurface() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M // >=API23 + && surfaceHolderCallback != null) { + binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); + surfaceHolderCallback.release(); + surfaceHolderCallback = null; } + Optional.ofNullable(player.getExoPlayer()).ifPresent(ExoPlayer::clearVideoSurface); + surfaceIsSetup = false; } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt index cbba0a75b..d0782e1a1 100644 --- a/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt +++ b/app/src/main/java/org/schabi/newpipe/views/player/PlayerFastSeekOverlay.kt @@ -38,14 +38,14 @@ class PlayerFastSeekOverlay(context: Context, attrs: AttributeSet?) : private var performListener: PerformListener? = null - fun performListener(listener: PerformListener) = apply { + fun performListener(listener: PerformListener?) = apply { performListener = listener } private var seekSecondsSupplier: () -> Int = { 0 } - fun seekSecondsSupplier(supplier: () -> Int) = apply { - seekSecondsSupplier = supplier + fun seekSecondsSupplier(supplier: (() -> Int)?) = apply { + seekSecondsSupplier = supplier ?: { 0 } } // Indicates whether this (double) tap is the first of a series From 0bba1d95dee627843bc97224e6e04260931fa9a8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:14:28 +0200 Subject: [PATCH 188/992] Move all notification-related calls to NotificationPlayerUi --- .../newpipe/player/NotificationUtil.java | 93 +++++++-------- .../org/schabi/newpipe/player/Player.java | 60 ++-------- .../schabi/newpipe/player/PlayerService.java | 4 - .../player/ui/NotificationPlayerUi.java | 109 +++++++++++++++++- 4 files changed, 155 insertions(+), 111 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java index f5caf2c79..e88defe7f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java @@ -45,22 +45,16 @@ public final class NotificationUtil { private static final boolean DEBUG = Player.DEBUG; private static final int NOTIFICATION_ID = 123789; - @Nullable private static NotificationUtil instance = null; - @NotificationConstants.Action private final int[] notificationSlots = NotificationConstants.SLOT_DEFAULTS.clone(); private NotificationManagerCompat notificationManager; private NotificationCompat.Builder notificationBuilder; - private NotificationUtil() { - } + private Player player; - public static NotificationUtil getInstance() { - if (instance == null) { - instance = new NotificationUtil(); - } - return instance; + public NotificationUtil(final Player player) { + this.player = player; } @@ -71,20 +65,18 @@ public final class NotificationUtil { /** * Creates the notification if it does not exist already and recreates it if forceRecreate is * true. Updates the notification with the data in the player. - * @param player the player currently open, to take data from * @param forceRecreate whether to force the recreation of the notification even if it already * exists */ - synchronized void createNotificationIfNeededAndUpdate(final Player player, - final boolean forceRecreate) { + public synchronized void createNotificationIfNeededAndUpdate(final boolean forceRecreate) { if (forceRecreate || notificationBuilder == null) { - notificationBuilder = createNotification(player); + notificationBuilder = createNotification(); } - updateNotification(player); + updateNotification(); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); } - private synchronized NotificationCompat.Builder createNotification(final Player player) { + private synchronized NotificationCompat.Builder createNotification() { if (DEBUG) { Log.d(TAG, "createNotification()"); } @@ -93,7 +85,7 @@ public final class NotificationUtil { new NotificationCompat.Builder(player.getContext(), player.getContext().getString(R.string.notification_channel_id)); - initializeNotificationSlots(player); + initializeNotificationSlots(); // count the number of real slots, to make sure compact slots indices are not out of bound int nonNothingSlotCount = 5; @@ -132,9 +124,8 @@ public final class NotificationUtil { /** * Updates the notification builder and the button icons depending on the playback state. - * @param player the player currently open, to take data from */ - private synchronized void updateNotification(final Player player) { + private synchronized void updateNotification() { if (DEBUG) { Log.d(TAG, "updateNotification()"); } @@ -145,17 +136,17 @@ public final class NotificationUtil { notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); - updateActions(notificationBuilder, player); + updateActions(notificationBuilder); final boolean showThumbnail = player.getPrefs().getBoolean( player.getContext().getString(R.string.show_thumbnail_key), true); if (showThumbnail) { - setLargeIcon(notificationBuilder, player); + setLargeIcon(notificationBuilder); } } @SuppressLint("RestrictedApi") - boolean shouldUpdateBufferingSlot() { + public boolean shouldUpdateBufferingSlot() { if (notificationBuilder == null) { // if there is no notification active, there is no point in updating it return false; @@ -173,22 +164,22 @@ public final class NotificationUtil { } - public void createNotificationAndStartForeground(final Player player, final Service service) { + public void createNotificationAndStartForeground() { if (notificationBuilder == null) { - notificationBuilder = createNotification(player); + notificationBuilder = createNotification(); } - updateNotification(player); + updateNotification(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - service.startForeground(NOTIFICATION_ID, notificationBuilder.build(), + player.getService().startForeground(NOTIFICATION_ID, notificationBuilder.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK); } else { - service.startForeground(NOTIFICATION_ID, notificationBuilder.build()); + player.getService().startForeground(NOTIFICATION_ID, notificationBuilder.build()); } } - void cancelNotificationAndStopForeground(final Service service) { - ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE); + public void cancelNotificationAndStopForeground() { + ServiceCompat.stopForeground(player.getService(), ServiceCompat.STOP_FOREGROUND_REMOVE); if (notificationManager != null) { notificationManager.cancel(NOTIFICATION_ID); @@ -202,7 +193,7 @@ public final class NotificationUtil { // ACTIONS ///////////////////////////////////////////////////// - private void initializeNotificationSlots(final Player player) { + private void initializeNotificationSlots() { for (int i = 0; i < 5; ++i) { notificationSlots[i] = player.getPrefs().getInt( player.getContext().getString(NotificationConstants.SLOT_PREF_KEYS[i]), @@ -211,7 +202,7 @@ public final class NotificationUtil { } @SuppressLint("RestrictedApi") - private void updateActions(final NotificationCompat.Builder builder, final Player player) { + private void updateActions(final NotificationCompat.Builder builder) { builder.mActions.clear(); for (int i = 0; i < 5; ++i) { addAction(builder, player, notificationSlots[i]); @@ -221,7 +212,7 @@ public final class NotificationUtil { private void addAction(final NotificationCompat.Builder builder, final Player player, @NotificationConstants.Action final int slot) { - final NotificationCompat.Action action = getAction(player, slot); + final NotificationCompat.Action action = getAction(slot); if (action != null) { builder.addAction(action); } @@ -229,41 +220,40 @@ public final class NotificationUtil { @Nullable private NotificationCompat.Action getAction( - final Player player, @NotificationConstants.Action final int selectedAction) { final int baseActionIcon = NotificationConstants.ACTION_ICONS[selectedAction]; switch (selectedAction) { case NotificationConstants.PREVIOUS: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS); case NotificationConstants.NEXT: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_next_description, ACTION_PLAY_NEXT); case NotificationConstants.REWIND: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_rewind_description, ACTION_FAST_REWIND); case NotificationConstants.FORWARD: - return getAction(player, baseActionIcon, + return getAction(baseActionIcon, R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD); case NotificationConstants.SMART_REWIND_PREVIOUS: if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) { - return getAction(player, R.drawable.exo_notification_previous, + return getAction(R.drawable.exo_notification_previous, R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS); } else { - return getAction(player, R.drawable.exo_controls_rewind, + return getAction(R.drawable.exo_controls_rewind, R.string.exo_controls_rewind_description, ACTION_FAST_REWIND); } case NotificationConstants.SMART_FORWARD_NEXT: if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) { - return getAction(player, R.drawable.exo_notification_next, + return getAction(R.drawable.exo_notification_next, R.string.exo_controls_next_description, ACTION_PLAY_NEXT); } else { - return getAction(player, R.drawable.exo_controls_fastforward, + return getAction(R.drawable.exo_controls_fastforward, R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD); } @@ -279,42 +269,42 @@ public final class NotificationUtil { case NotificationConstants.PLAY_PAUSE: if (player.getCurrentState() == Player.STATE_COMPLETED) { - return getAction(player, R.drawable.ic_replay, + return getAction(R.drawable.ic_replay, R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE); } else if (player.isPlaying() || player.getCurrentState() == Player.STATE_PREFLIGHT || player.getCurrentState() == Player.STATE_BLOCKED || player.getCurrentState() == Player.STATE_BUFFERING) { - return getAction(player, R.drawable.exo_notification_pause, + return getAction(R.drawable.exo_notification_pause, R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE); } else { - return getAction(player, R.drawable.exo_notification_play, + return getAction(R.drawable.exo_notification_play, R.string.exo_controls_play_description, ACTION_PLAY_PAUSE); } case NotificationConstants.REPEAT: if (player.getRepeatMode() == REPEAT_MODE_ALL) { - return getAction(player, R.drawable.exo_media_action_repeat_all, + return getAction(R.drawable.exo_media_action_repeat_all, R.string.exo_controls_repeat_all_description, ACTION_REPEAT); } else if (player.getRepeatMode() == REPEAT_MODE_ONE) { - return getAction(player, R.drawable.exo_media_action_repeat_one, + return getAction(R.drawable.exo_media_action_repeat_one, R.string.exo_controls_repeat_one_description, ACTION_REPEAT); } else /* player.getRepeatMode() == REPEAT_MODE_OFF */ { - return getAction(player, R.drawable.exo_media_action_repeat_off, + return getAction(R.drawable.exo_media_action_repeat_off, R.string.exo_controls_repeat_off_description, ACTION_REPEAT); } case NotificationConstants.SHUFFLE: if (player.getPlayQueue() != null && player.getPlayQueue().isShuffled()) { - return getAction(player, R.drawable.exo_controls_shuffle_on, + return getAction(R.drawable.exo_controls_shuffle_on, R.string.exo_controls_shuffle_on_description, ACTION_SHUFFLE); } else { - return getAction(player, R.drawable.exo_controls_shuffle_off, + return getAction(R.drawable.exo_controls_shuffle_off, R.string.exo_controls_shuffle_off_description, ACTION_SHUFFLE); } case NotificationConstants.CLOSE: - return getAction(player, R.drawable.ic_close, + return getAction(R.drawable.ic_close, R.string.close, ACTION_CLOSE); case NotificationConstants.NOTHING: @@ -324,8 +314,7 @@ public final class NotificationUtil { } } - private NotificationCompat.Action getAction(final Player player, - @DrawableRes final int drawable, + private NotificationCompat.Action getAction(@DrawableRes final int drawable, @StringRes final int title, final String intentAction) { return new NotificationCompat.Action(drawable, player.getContext().getString(title), @@ -353,7 +342,7 @@ public final class NotificationUtil { // BITMAP ///////////////////////////////////////////////////// - private void setLargeIcon(final NotificationCompat.Builder builder, final Player player) { + private void setLargeIcon(final NotificationCompat.Builder builder) { final boolean scaleImageToSquareAspectRatio = player.getPrefs().getBoolean( player.getContext().getString(R.string.scale_to_square_image_in_notifications_key), false); 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 78e93970c..e2732f4d0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -38,7 +38,6 @@ import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; -import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; @@ -620,7 +619,7 @@ public final class Player implements PlaybackListener, Listener { } private void setRecovery(final int queuePos, final long windowPos) { - if (playQueue.size() <= queuePos) { + if (playQueue == null || playQueue.size() <= queuePos) { return; } @@ -735,9 +734,6 @@ public final class Player implements PlaybackListener, Listener { case ACTION_SHUFFLE: toggleShuffleModeEnabled(); break; - case ACTION_RECREATE_NOTIFICATION: - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); - break; case Intent.ACTION_CONFIGURATION_CHANGED: assureCorrectAppLanguage(service); if (DEBUG) { @@ -797,8 +793,6 @@ public final class Player implements PlaybackListener, Listener { } currentThumbnail = bitmap; - NotificationUtil.getInstance() - .createNotificationIfNeededAndUpdate(Player.this, false); // there is a new thumbnail, so changed the end screen thumbnail, too. UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); } @@ -807,8 +801,7 @@ public final class Player implements PlaybackListener, Listener { public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); currentThumbnail = null; - NotificationUtil.getInstance() - .createNotificationIfNeededAndUpdate(Player.this, false); + UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); } @Override @@ -1082,8 +1075,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onBlocked); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onPlaying() { @@ -1095,8 +1086,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onPlaying); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onBuffering() { @@ -1105,10 +1094,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onBuffering); - - if (NotificationUtil.getInstance().shouldUpdateBufferingSlot()) { - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } } private void onPaused() { @@ -1121,24 +1106,13 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onPaused); - - // Remove running notification when user does not want minimization to background or popup - if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE - && videoPlayerSelected()) { - NotificationUtil.getInstance().cancelNotificationAndStopForeground(service); - } else { - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } } private void onPausedSeek() { if (DEBUG) { Log.d(TAG, "onPausedSeek() called"); } - UIs.call(PlayerUi::onPausedSeek); - - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } private void onCompleted() { @@ -1150,7 +1124,6 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(PlayerUi::onCompleted); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); if (playQueue.getIndex() < playQueue.size() - 1) { playQueue.offsetIndex(+1); @@ -1190,7 +1163,7 @@ public final class Player implements PlaybackListener, Listener { + "repeatMode = [" + repeatMode + "]"); } UIs.call(playerUi -> playerUi.onRepeatModeChanged(repeatMode)); - onShuffleOrRepeatModeChanged(); + notifyPlaybackUpdateToListeners(); } @Override @@ -1209,7 +1182,7 @@ public final class Player implements PlaybackListener, Listener { } UIs.call(playerUi -> playerUi.onShuffleModeEnabledChanged(shuffleModeEnabled)); - onShuffleOrRepeatModeChanged(); + notifyPlaybackUpdateToListeners(); } public void toggleShuffleModeEnabled() { @@ -1217,11 +1190,6 @@ public final class Player implements PlaybackListener, Listener { simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); } } - - private void onShuffleOrRepeatModeChanged() { - notifyPlaybackUpdateToListeners(); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } //endregion @@ -1806,12 +1774,15 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Metadata - private void onMetadataChanged(@NonNull final StreamInfo info) { + private void updateMetadataWith(@NonNull final StreamInfo info) { if (DEBUG) { Log.d(TAG, "Playback - onMetadataChanged() called, playing: " + info.getName()); } + if (exoPlayerIsNull()) { + return; + } - UIs.call(playerUi -> playerUi.onMetadataChanged(info)); + maybeAutoQueueNextStream(info); initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); @@ -1826,17 +1797,7 @@ public final class Player implements PlaybackListener, Listener { ); notifyMetadataUpdateToListeners(); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); - } - - private void updateMetadataWith(@NonNull final StreamInfo streamInfo) { - if (exoPlayerIsNull()) { - return; - } - - maybeAutoQueueNextStream(streamInfo); - onMetadataChanged(streamInfo); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true); + UIs.call(playerUi -> playerUi.onMetadataChanged(info)); } @NonNull @@ -1925,7 +1886,6 @@ public final class Player implements PlaybackListener, Listener { public void onPlayQueueEdited() { notifyPlaybackUpdateToListeners(); UIs.call(PlayerUi::onPlayQueueEdited); - NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, false); } @Override // own playback listener 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 cf83dc5c2..7bf918c73 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -88,9 +88,6 @@ public final class PlayerService extends Service { ThemeHelper.setTheme(this); player = new Player(this); - /*final MainPlayerUi mainPlayerUi = new MainPlayerUi(player, - PlayerBinding.inflate(LayoutInflater.from(this))); - player.UIs().add(mainPlayerUi);*/ } @Override @@ -159,7 +156,6 @@ public final class PlayerService extends Service { } public void stopService() { - NotificationUtil.getInstance().cancelNotificationAndStopForeground(this); cleanup(); stopSelf(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java index 40c83c6c7..5736eca3b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java @@ -1,26 +1,125 @@ package org.schabi.newpipe.player.ui; -import androidx.annotation.NonNull; +import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; +import android.content.Intent; +import android.graphics.Bitmap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.Player.RepeatMode; + +import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.NotificationUtil; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.helper.PlayerHelper; public final class NotificationPlayerUi extends PlayerUi { - boolean foregroundNotificationAlreadyCreated = false; + private boolean foregroundNotificationAlreadyCreated = false; + private final NotificationUtil notificationUtil; public NotificationPlayerUi(@NonNull final Player player) { super(player); + notificationUtil = new NotificationUtil(player); } @Override public void initPlayer() { super.initPlayer(); if (!foregroundNotificationAlreadyCreated) { - NotificationUtil.getInstance() - .createNotificationAndStartForeground(player, player.getService()); + notificationUtil.createNotificationAndStartForeground(); foregroundNotificationAlreadyCreated = true; } } - // TODO TODO on destroy remove foreground + @Override + public void destroy() { + super.destroy(); + notificationUtil.cancelNotificationAndStopForeground(); + } + + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBlocked() { + super.onBlocked(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onPlaying() { + super.onPlaying(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBuffering() { + super.onBuffering(); + if (notificationUtil.shouldUpdateBufferingSlot()) { + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + } + + @Override + public void onPaused() { + super.onPaused(); + + // Remove running notification when user does not want minimization to background or popup + if (PlayerHelper.getMinimizeOnExitAction(context) == MINIMIZE_ON_EXIT_MODE_NONE + && player.videoPlayerSelected()) { + notificationUtil.cancelNotificationAndStopForeground(); + } else { + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + } + + @Override + public void onPausedSeek() { + super.onPausedSeek(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onCompleted() { + super.onCompleted(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onRepeatModeChanged(@RepeatMode final int repeatMode) { + super.onRepeatModeChanged(repeatMode); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onShuffleModeEnabledChanged(final boolean shuffleModeEnabled) { + super.onShuffleModeEnabledChanged(shuffleModeEnabled); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } + + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + if (ACTION_RECREATE_NOTIFICATION.equals(intent.getAction())) { + notificationUtil.createNotificationIfNeededAndUpdate(true); + } + } + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + notificationUtil.createNotificationIfNeededAndUpdate(true); + } + + @Override + public void onPlayQueueEdited() { + super.onPlayQueueEdited(); + notificationUtil.createNotificationIfNeededAndUpdate(false); + } } From 90a89f8ca555433b1e8f9f9d2713d0f7667060be Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:40:55 +0200 Subject: [PATCH 189/992] Move player-notification files into their package --- .../org/schabi/newpipe/player/Player.java | 20 ++++++------ .../schabi/newpipe/player/PlayerService.java | 25 --------------- .../NotificationConstants.java | 32 +++++++++++++++++-- .../NotificationPlayerUi.java | 6 ++-- .../{ => notification}/NotificationUtil.java | 25 +++++++-------- .../newpipe/player/ui/MainPlayerUi.java | 2 +- .../custom/NotificationActionsPreference.java | 6 ++-- 7 files changed, 60 insertions(+), 56 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{ => notification}/NotificationConstants.java (82%) rename app/src/main/java/org/schabi/newpipe/player/{ui => notification}/NotificationPlayerUi.java (94%) rename app/src/main/java/org/schabi/newpipe/player/{ => notification}/NotificationUtil.java (94%) 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 e2732f4d0..55600b956 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -29,21 +29,21 @@ import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static com.google.android.exoplayer2.Player.RepeatMode; import static org.schabi.newpipe.extractor.ServiceList.YouTube; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; -import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; -import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; -import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_REPEAT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE; import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex; import static org.schabi.newpipe.util.ListHelper.getResolutionIndex; import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; @@ -125,7 +125,7 @@ import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; import org.schabi.newpipe.player.ui.MainPlayerUi; -import org.schabi.newpipe.player.ui.NotificationPlayerUi; +import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.player.ui.PlayerUiList; import org.schabi.newpipe.player.ui.PopupPlayerUi; 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 7bf918c73..b5014eeed 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -28,8 +28,6 @@ import android.os.Binder; import android.os.IBinder; import android.util.Log; -import org.schabi.newpipe.App; -import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.ThemeHelper; @@ -52,29 +50,6 @@ public final class PlayerService extends Service { POPUP } - /*////////////////////////////////////////////////////////////////////////// - // Notification - //////////////////////////////////////////////////////////////////////////*/ - - static final String ACTION_CLOSE - = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; - public static final String ACTION_PLAY_PAUSE - = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; - static final String ACTION_REPEAT - = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; - static final String ACTION_PLAY_NEXT - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT"; - static final String ACTION_PLAY_PREVIOUS - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; - static final String ACTION_FAST_REWIND - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND"; - static final String ACTION_FAST_FORWARD - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD"; - static final String ACTION_SHUFFLE - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE"; - public static final String ACTION_RECREATE_NOTIFICATION - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; - /*////////////////////////////////////////////////////////////////////////// // Service's LifeCycle //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java similarity index 82% rename from app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java rename to app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index 6c9858d1b..53ef752bd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player; +package org.schabi.newpipe.player.notification; import android.content.Context; import android.content.SharedPreferences; @@ -7,6 +7,7 @@ import androidx.annotation.DrawableRes; import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.util.Localization; @@ -20,7 +21,34 @@ import java.util.TreeSet; public final class NotificationConstants { - private NotificationConstants() { } + private NotificationConstants() { + } + + + + /*////////////////////////////////////////////////////////////////////////// + // Intent actions + //////////////////////////////////////////////////////////////////////////*/ + + public static final String ACTION_CLOSE + = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; + public static final String ACTION_PLAY_PAUSE + = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; + public static final String ACTION_REPEAT + = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; + public static final String ACTION_PLAY_NEXT + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT"; + public static final String ACTION_PLAY_PREVIOUS + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; + public static final String ACTION_FAST_REWIND + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND"; + public static final String ACTION_FAST_FORWARD + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD"; + public static final String ACTION_SHUFFLE + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE"; + public static final String ACTION_RECREATE_NOTIFICATION + = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; + public static final int NOTHING = 0; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java similarity index 94% rename from app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java rename to app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java index 5736eca3b..ed678a18c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/NotificationPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationPlayerUi.java @@ -1,7 +1,7 @@ -package org.schabi.newpipe.player.ui; +package org.schabi.newpipe.player.notification; -import static org.schabi.newpipe.player.PlayerService.ACTION_RECREATE_NOTIFICATION; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION; import android.content.Intent; import android.graphics.Bitmap; @@ -12,9 +12,9 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.Player.RepeatMode; import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.player.NotificationUtil; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.helper.PlayerHelper; +import org.schabi.newpipe.player.ui.PlayerUi; public final class NotificationPlayerUi extends PlayerUi { private boolean foregroundNotificationAlreadyCreated = false; diff --git a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java similarity index 94% rename from app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java rename to app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index e88defe7f..5f0052453 100644 --- a/app/src/main/java/org/schabi/newpipe/player/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -1,8 +1,7 @@ -package org.schabi.newpipe.player; +package org.schabi.newpipe.player.notification; import android.annotation.SuppressLint; import android.app.PendingIntent; -import android.app.Service; import android.content.Intent; import android.content.pm.ServiceInfo; import android.graphics.Bitmap; @@ -19,6 +18,7 @@ import androidx.core.content.ContextCompat; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.NavigationHelper; import java.util.List; @@ -26,14 +26,14 @@ import java.util.List; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.PlayerService.ACTION_CLOSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_FORWARD; -import static org.schabi.newpipe.player.PlayerService.ACTION_FAST_REWIND; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_NEXT; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PREVIOUS; -import static org.schabi.newpipe.player.PlayerService.ACTION_REPEAT; -import static org.schabi.newpipe.player.PlayerService.ACTION_SHUFFLE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_FORWARD; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_FAST_REWIND; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_NEXT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PREVIOUS; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_REPEAT; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE; /** * This is a utility class for player notifications. @@ -51,7 +51,7 @@ public final class NotificationUtil { private NotificationManagerCompat notificationManager; private NotificationCompat.Builder notificationBuilder; - private Player player; + private final Player player; public NotificationUtil(final Player player) { this.player = player; @@ -205,12 +205,11 @@ public final class NotificationUtil { private void updateActions(final NotificationCompat.Builder builder) { builder.mActions.clear(); for (int i = 0; i < 5; ++i) { - addAction(builder, player, notificationSlots[i]); + addAction(builder, notificationSlots[i]); } } private void addAction(final NotificationCompat.Builder builder, - final Player player, @NotificationConstants.Action final int slot) { final NotificationCompat.Action action = getAction(slot); if (action != null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 7c60671dd..80230d0f7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -5,13 +5,13 @@ import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; import static org.schabi.newpipe.ktx.ViewUtils.animate; import static org.schabi.newpipe.player.Player.STATE_COMPLETED; import static org.schabi.newpipe.player.Player.STATE_PAUSED; -import static org.schabi.newpipe.player.PlayerService.ACTION_PLAY_PAUSE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimizeOnExitAction; import static org.schabi.newpipe.player.helper.PlayerHelper.getTimeString; import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_PLAY_PAUSE; import android.app.Activity; import android.content.Context; diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 0eb58f7a9..dfcf2e597 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.settings.custom; +import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_RECREATE_NOTIFICATION; + import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -27,7 +29,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; import org.schabi.newpipe.player.PlayerService; -import org.schabi.newpipe.player.NotificationConstants; +import org.schabi.newpipe.player.notification.NotificationConstants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; @@ -61,7 +63,7 @@ public class NotificationActionsPreference extends Preference { public void onDetached() { super.onDetached(); saveChanges(); - getContext().sendBroadcast(new Intent(PlayerService.ACTION_RECREATE_NOTIFICATION)); + getContext().sendBroadcast(new Intent(ACTION_RECREATE_NOTIFICATION)); } From 8c26403e91de46631fffceeb1216bb20c630033f Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:42:22 +0200 Subject: [PATCH 190/992] Remove unused PlayerState --- .../schabi/newpipe/player/PlayerState.java | 79 ------------------- 1 file changed, 79 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/PlayerState.java diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerState.java b/app/src/main/java/org/schabi/newpipe/player/PlayerState.java deleted file mode 100644 index af875a32b..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerState.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.schabi.newpipe.player; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.schabi.newpipe.player.playqueue.PlayQueue; - -import java.io.Serializable; - -public class PlayerState implements Serializable { - - @NonNull - private final PlayQueue playQueue; - private final int repeatMode; - private final float playbackSpeed; - private final float playbackPitch; - @Nullable - private final String playbackQuality; - private final boolean playbackSkipSilence; - private final boolean wasPlaying; - - PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode, - final float playbackSpeed, final float playbackPitch, - final boolean playbackSkipSilence, final boolean wasPlaying) { - this(playQueue, repeatMode, playbackSpeed, playbackPitch, null, - playbackSkipSilence, wasPlaying); - } - - PlayerState(@NonNull final PlayQueue playQueue, final int repeatMode, - final float playbackSpeed, final float playbackPitch, - @Nullable final String playbackQuality, final boolean playbackSkipSilence, - final boolean wasPlaying) { - this.playQueue = playQueue; - this.repeatMode = repeatMode; - this.playbackSpeed = playbackSpeed; - this.playbackPitch = playbackPitch; - this.playbackQuality = playbackQuality; - this.playbackSkipSilence = playbackSkipSilence; - this.wasPlaying = wasPlaying; - } - - /*////////////////////////////////////////////////////////////////////////// - // Serdes - //////////////////////////////////////////////////////////////////////////*/ - - /*////////////////////////////////////////////////////////////////////////// - // Getters - //////////////////////////////////////////////////////////////////////////*/ - - @NonNull - public PlayQueue getPlayQueue() { - return playQueue; - } - - public int getRepeatMode() { - return repeatMode; - } - - public float getPlaybackSpeed() { - return playbackSpeed; - } - - public float getPlaybackPitch() { - return playbackPitch; - } - - @Nullable - public String getPlaybackQuality() { - return playbackQuality; - } - - public boolean isPlaybackSkipSilence() { - return playbackSkipSilence; - } - - public boolean wasPlaying() { - return wasPlaying; - } -} From 6fb02569978c8126cb24376419980a8321843b33 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:43:54 +0200 Subject: [PATCH 191/992] Remove unused PlayerServiceBinder --- .../newpipe/player/PlayQueueActivity.java | 4 +--- .../newpipe/player/PlayerServiceBinder.java | 17 ----------------- 2 files changed, 1 insertion(+), 20 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java 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 d00e6265e..cdba900f9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -207,9 +207,7 @@ public final class PlayQueueActivity extends AppCompatActivity public void onServiceConnected(final ComponentName name, final IBinder service) { Log.d(TAG, "Player service is connected"); - if (service instanceof PlayerServiceBinder) { - player = ((PlayerServiceBinder) service).getPlayerInstance(); - } else if (service instanceof PlayerService.LocalBinder) { + if (service instanceof PlayerService.LocalBinder) { player = ((PlayerService.LocalBinder) service).getPlayer(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java b/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java deleted file mode 100644 index 5c28c6c7b..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerServiceBinder.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.schabi.newpipe.player; - -import android.os.Binder; - -import androidx.annotation.NonNull; - -class PlayerServiceBinder extends Binder { - private final Player player; - - PlayerServiceBinder(@NonNull final Player player) { - this.player = player; - } - - Player getPlayerInstance() { - return player; - } -} From fa25ecf52143a3cb8b695db7c25799de320b1e5f Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 18:55:23 +0200 Subject: [PATCH 192/992] Add comment about broadcast receiver --- app/src/main/java/org/schabi/newpipe/player/Player.java | 6 ++++++ .../main/java/org/schabi/newpipe/player/ui/PlayerUi.java | 4 ++++ 2 files changed, 10 insertions(+) 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 55600b956..b0fed3d7d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -663,6 +663,12 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + /** + * This function prepares the broadcast receiver and is called only in the constructor. + * Therefore if you want any PlayerUi to receive a broadcast action, you should add it here, + * even if that player ui might never be added to the player. In that case the received + * broadcast would not do anything. + */ private void setupBroadcastReceiver() { if (DEBUG) { Log.d(TAG, "setupBroadcastReceiver() called"); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 15b468fb7..81e93ca23 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -55,6 +55,10 @@ public abstract class PlayerUi { public void onFragmentListenerSet() { } + /** + * If you want to register new broadcast actions to receive here, add them to + * {@link Player#setupBroadcastReceiver()}. + */ public void onBroadcastReceived(final Intent intent) { } From 6559416bd8e0857a32b00c097638be9dc2eec88b Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Apr 2022 23:07:29 +0200 Subject: [PATCH 193/992] Improve //region comments in player UIs --- .../newpipe/player/ui/MainPlayerUi.java | 73 ++++++---- .../newpipe/player/ui/PopupPlayerUi.java | 80 +++++++---- .../newpipe/player/ui/VideoPlayerUi.java | 131 ++++++++++-------- 3 files changed, 173 insertions(+), 111 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 80230d0f7..c62382782 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -88,6 +88,12 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh // fullscreen player private ItemTouchHelper itemTouchHelper; + + /*////////////////////////////////////////////////////////////////////////// + // Constructor, setup, destroy + //////////////////////////////////////////////////////////////////////////*/ + //region Constructor, setup, destroy + public MainPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player, playerBinding); @@ -272,12 +278,14 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh resources.getDimensionPixelSize(R.dimen.player_main_buttons_padding) ); } + //endregion /*////////////////////////////////////////////////////////////////////////// // Broadcast receiver //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + @Override public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); @@ -313,6 +321,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh // Fragment binding //////////////////////////////////////////////////////////////////////////*/ //region Fragment binding + @Override public void onFragmentListenerSet() { super.onFragmentListenerSet(); @@ -351,13 +360,11 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } //endregion - private void showHideKodiButton() { - // show kodi button if it supports the current service and it is enabled in settings - @Nullable final PlayQueue playQueue = player.getPlayQueue(); - binding.playWithKodi.setVisibility(playQueue != null && playQueue.getItem() != null - && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) - ? View.VISIBLE : View.GONE); - } + + /*////////////////////////////////////////////////////////////////////////// + // Playback states + //////////////////////////////////////////////////////////////////////////*/ + //region Playback states @Override public void onUpdateProgress(final int currentProgress, @@ -373,6 +380,22 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } } + @Override + public void onPlaying() { + super.onPlaying(); + checkLandscape(); + } + + @Override + public void onCompleted() { + super.onCompleted(); + if (isFullscreen) { + toggleFullscreen(); + } + } + //endregion + + /*////////////////////////////////////////////////////////////////////////// // Controls showing / hiding //////////////////////////////////////////////////////////////////////////*/ @@ -457,22 +480,21 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh return Math.min(bitmap.getHeight(), screenHeight); } } + + private void showHideKodiButton() { + // show kodi button if it supports the current service and it is enabled in settings + @Nullable final PlayQueue playQueue = player.getPlayQueue(); + binding.playWithKodi.setVisibility(playQueue != null && playQueue.getItem() != null + && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId()) + ? View.VISIBLE : View.GONE); + } //endregion - @Override - public void onPlaying() { - super.onPlaying(); - checkLandscape(); - } - - @Override - public void onCompleted() { - super.onCompleted(); - if (isFullscreen) { - toggleFullscreen(); - } - } + /*////////////////////////////////////////////////////////////////////////// + // Captions (text tracks) + //////////////////////////////////////////////////////////////////////////*/ + //region Captions (text tracks) @Override protected void setupSubtitleView(float captionScale) { @@ -482,8 +504,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.subtitleView.setFixedTextSize( TypedValue.COMPLEX_UNIT_PX, minimumLength / captionRatioInverse); } - - + //endregion /*////////////////////////////////////////////////////////////////////////// @@ -798,6 +819,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh // Click listeners //////////////////////////////////////////////////////////////////////////*/ //region Click listeners + @Override public void onClick(final View v) { if (v.getId() == binding.screenRotationButton.getId()) { @@ -855,9 +877,9 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh /*////////////////////////////////////////////////////////////////////////// - // Video size, resize, orientation, fullscreen + // Video size, orientation, fullscreen //////////////////////////////////////////////////////////////////////////*/ - //region Video size, resize, orientation, fullscreen + //region Video size, orientation, fullscreen private void setupScreenRotationButton() { binding.screenRotationButton.setVisibility(globalScreenOrientationLocked(context) @@ -941,9 +963,6 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh // Getters //////////////////////////////////////////////////////////////////////////*/ //region Getters - public PlayerBinding getBinding() { - return binding; - } public Optional getParentActivity() { final ViewParent rootParent = binding.getRoot().getParent(); 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 7df9102b7..43440b873 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 @@ -8,7 +8,6 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutP import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; -import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; @@ -61,6 +60,12 @@ public final class PopupPlayerUi extends VideoPlayerUi { private WindowManager.LayoutParams popupLayoutParams; // null if player is not popup private final WindowManager windowManager; + + /*////////////////////////////////////////////////////////////////////////// + // Constructor, setup, destroy + //////////////////////////////////////////////////////////////////////////*/ + //region Constructor, setup, destroy + public PopupPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player, playerBinding); @@ -173,11 +178,14 @@ public final class PopupPlayerUi extends VideoPlayerUi { super.destroy(); removePopupFromView(); } + //endregion + /*////////////////////////////////////////////////////////////////////////// // Broadcast receiver //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + @Override public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); @@ -200,6 +208,11 @@ public final class PopupPlayerUi extends VideoPlayerUi { //endregion + /*////////////////////////////////////////////////////////////////////////// + // Popup position and size + //////////////////////////////////////////////////////////////////////////*/ + //region Popup position and size + /** * Check if {@link #popupLayoutParams}' position is within a arbitrary boundary * that goes from (0, 0) to (screenWidth, screenHeight). @@ -272,16 +285,19 @@ public final class PopupPlayerUi extends VideoPlayerUi { windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); } - private void changePopupWindowFlags(final int flags) { - if (DEBUG) { - Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); - } - - if (!anyPopupViewIsNull()) { - popupLayoutParams.flags = flags; - windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); - } + @Override + protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { + // no need for the end screen thumbnail to be resized on popup player: it's only needed + // for the main player so that it is enlarged correctly inside the fragment + return bitmap.getHeight(); } + //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // Popup closing + //////////////////////////////////////////////////////////////////////////*/ + //region Popup closing public void closePopup() { if (DEBUG) { @@ -351,23 +367,22 @@ public final class PopupPlayerUi extends VideoPlayerUi { } }).start(); } + //endregion - @Override - protected float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap) { - // no need for the end screen thumbnail to be resized on popup player: it's only needed - // for the main player so that it is enlarged correctly inside the fragment - return bitmap.getHeight(); - } + /*////////////////////////////////////////////////////////////////////////// + // Playback states + //////////////////////////////////////////////////////////////////////////*/ + //region Playback states - private boolean popupHasParent() { - return binding != null - && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams - && binding.getRoot().getParent() != null; - } + private void changePopupWindowFlags(final int flags) { + if (DEBUG) { + Log.d(TAG, "changePopupWindowFlags() called with: flags = [" + flags + "]"); + } - private boolean anyPopupViewIsNull() { - return popupLayoutParams == null || windowManager == null - || binding.getRoot().getParent() == null; + if (!anyPopupViewIsNull()) { + popupLayoutParams.flags = flags; + windowManager.updateViewLayout(binding.getRoot(), popupLayoutParams); + } } @Override @@ -400,11 +415,14 @@ public final class PopupPlayerUi extends VideoPlayerUi { playbackSpeedPopupMenu.show(); isSomePopupMenuVisible = true; } + //endregion + /*////////////////////////////////////////////////////////////////////////// // Gestures //////////////////////////////////////////////////////////////////////////*/ //region Gestures + private int distanceFromCloseButton(@NonNull final MotionEvent popupMotionEvent) { final int closeOverlayButtonX = closeOverlayBinding.closeButton.getLeft() + closeOverlayBinding.closeButton.getWidth() / 2; @@ -433,7 +451,19 @@ public final class PopupPlayerUi extends VideoPlayerUi { /*////////////////////////////////////////////////////////////////////////// // Getters //////////////////////////////////////////////////////////////////////////*/ - //region Gestures + //region Getters + + private boolean popupHasParent() { + return binding != null + && binding.getRoot().getLayoutParams() instanceof WindowManager.LayoutParams + && binding.getRoot().getParent() != null; + } + + private boolean anyPopupViewIsNull() { + return popupLayoutParams == null || windowManager == null + || binding.getRoot().getParent() == null; + } + public PlayerPopupCloseOverlayBinding getCloseOverlayBinding() { return closeOverlayBinding; } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 24cdb8908..f4ebc3304 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -135,6 +135,12 @@ public abstract class VideoPlayerUi extends PlayerUi @NonNull private final SeekbarPreviewThumbnailHolder seekbarPreviewThumbnailHolder = new SeekbarPreviewThumbnailHolder(); + + /*////////////////////////////////////////////////////////////////////////// + // Constructor, setup, destroy + //////////////////////////////////////////////////////////////////////////*/ + //region Constructor, setup, destroy + public VideoPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player); @@ -142,11 +148,6 @@ public abstract class VideoPlayerUi extends PlayerUi setupFromView(); } - - /*////////////////////////////////////////////////////////////////////////// - // Setup - //////////////////////////////////////////////////////////////////////////*/ - //region Setup public void setupFromView() { initViews(); initListeners(); @@ -414,6 +415,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Broadcast receiver //////////////////////////////////////////////////////////////////////////*/ //region Broadcast receiver + @Override public void onBroadcastReceived(final Intent intent) { super.onBroadcastReceived(intent); @@ -433,6 +435,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Thumbnail //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail + /** * Scale the player audio / end screen thumbnail down if necessary. *

@@ -481,6 +484,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Progress loop and updates //////////////////////////////////////////////////////////////////////////*/ //region Progress loop and updates + @Override public void onUpdateProgress(final int currentProgress, final int duration, @@ -744,6 +748,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Playback states //////////////////////////////////////////////////////////////////////////*/ //region Playback states + @Override public void onPrepared() { super.onPrepared(); @@ -885,7 +890,8 @@ public abstract class VideoPlayerUi extends PlayerUi /*////////////////////////////////////////////////////////////////////////// // Repeat, shuffle, mute //////////////////////////////////////////////////////////////////////////*/ - //region Repeat and shuffle + //region Repeat, shuffle, mute + public void onRepeatClicked() { if (DEBUG) { Log.d(TAG, "onRepeatClicked() called"); @@ -945,52 +951,9 @@ public abstract class VideoPlayerUi extends PlayerUi /*////////////////////////////////////////////////////////////////////////// - // ExoPlayer listeners (that didn't fit in other categories) + // Other player listeners //////////////////////////////////////////////////////////////////////////*/ - //region ExoPlayer listeners (that didn't fit in other categories) - @Override - public void onTextTracksChanged(@NonNull final Tracks currentTracks) { - super.onTextTracksChanged(currentTracks); - - final boolean trackTypeTextSupported = !currentTracks.containsType(C.TRACK_TYPE_TEXT) - || currentTracks.isTypeSupported(C.TRACK_TYPE_TEXT, false); - if (getPlayer().getTrackSelector().getCurrentMappedTrackInfo() == null - || !trackTypeTextSupported) { - binding.captionTextView.setVisibility(View.GONE); - return; - } - - // Extract all loaded languages - final List textTracks = currentTracks - .getGroups() - .stream() - .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) - .collect(Collectors.toList()); - final List availableLanguages = textTracks.stream() - .map(Tracks.Group::getMediaTrackGroup) - .filter(textTrack -> textTrack.length > 0) - .map(textTrack -> textTrack.getFormat(0).language) - .collect(Collectors.toList()); - - // Find selected text track - final Optional selectedTracks = textTracks.stream() - .filter(Tracks.Group::isSelected) - .filter(info -> info.getMediaTrackGroup().length >= 1) - .map(info -> info.getMediaTrackGroup().getFormat(0)) - .findFirst(); - - // Build UI - buildCaptionMenu(availableLanguages); - //noinspection SimplifyOptionalCallChains - if (player.getTrackSelector().getParameters().getRendererDisabled( - player.getCaptionRendererIndex()) || !selectedTracks.isPresent()) { - binding.captionTextView.setText(R.string.caption_none); - } else { - binding.captionTextView.setText(selectedTracks.get().language); - } - binding.captionTextView.setVisibility( - availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); - } + //region Other player listeners @Override public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { @@ -1004,12 +967,6 @@ public abstract class VideoPlayerUi extends PlayerUi //TODO check if this causes black screen when switching to fullscreen animate(binding.surfaceForeground, false, DEFAULT_CONTROLS_DURATION); } - - @Override - public void onCues(@NonNull List cues) { - super.onCues(cues); - binding.subtitleView.setCues(cues); - } //endregion @@ -1017,6 +974,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Metadata & stream related views //////////////////////////////////////////////////////////////////////////*/ //region Metadata & stream related views + @Override public void onMetadataChanged(@NonNull final StreamInfo info) { super.onMetadataChanged(info); @@ -1092,6 +1050,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Popup menus ("popup" means that they pop up, not that they belong to the popup player) //////////////////////////////////////////////////////////////////////////*/ //region Popup menus ("popup" means that they pop up, not that they belong to the popup player) + private void buildQualityMenu() { if (qualityPopupMenu == null) { return; @@ -1315,6 +1274,57 @@ public abstract class VideoPlayerUi extends PlayerUi // Captions (text tracks) //////////////////////////////////////////////////////////////////////////*/ //region Captions (text tracks) + + @Override + public void onTextTracksChanged(@NonNull final Tracks currentTracks) { + super.onTextTracksChanged(currentTracks); + + final boolean trackTypeTextSupported = !currentTracks.containsType(C.TRACK_TYPE_TEXT) + || currentTracks.isTypeSupported(C.TRACK_TYPE_TEXT, false); + if (getPlayer().getTrackSelector().getCurrentMappedTrackInfo() == null + || !trackTypeTextSupported) { + binding.captionTextView.setVisibility(View.GONE); + return; + } + + // Extract all loaded languages + final List textTracks = currentTracks + .getGroups() + .stream() + .filter(trackGroupInfo -> C.TRACK_TYPE_TEXT == trackGroupInfo.getType()) + .collect(Collectors.toList()); + final List availableLanguages = textTracks.stream() + .map(Tracks.Group::getMediaTrackGroup) + .filter(textTrack -> textTrack.length > 0) + .map(textTrack -> textTrack.getFormat(0).language) + .collect(Collectors.toList()); + + // Find selected text track + final Optional selectedTracks = textTracks.stream() + .filter(Tracks.Group::isSelected) + .filter(info -> info.getMediaTrackGroup().length >= 1) + .map(info -> info.getMediaTrackGroup().getFormat(0)) + .findFirst(); + + // Build UI + buildCaptionMenu(availableLanguages); + //noinspection SimplifyOptionalCallChains + if (player.getTrackSelector().getParameters().getRendererDisabled( + player.getCaptionRendererIndex()) || !selectedTracks.isPresent()) { + binding.captionTextView.setText(R.string.caption_none); + } else { + binding.captionTextView.setText(selectedTracks.get().language); + } + binding.captionTextView.setVisibility( + availableLanguages.isEmpty() ? View.GONE : View.VISIBLE); + } + + @Override + public void onCues(@NonNull List cues) { + super.onCues(cues); + binding.subtitleView.setCues(cues); + } + private void setupSubtitleView() { setupSubtitleView(PlayerHelper.getCaptionScale(context)); final CaptionStyleCompat captionStyle = PlayerHelper.getCaptionStyle(context); @@ -1330,6 +1340,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Click listeners //////////////////////////////////////////////////////////////////////////*/ //region Click listeners + @Override public void onClick(final View v) { if (DEBUG) { @@ -1493,9 +1504,10 @@ public abstract class VideoPlayerUi extends PlayerUi /*////////////////////////////////////////////////////////////////////////// - // Video size, resize, orientation, fullscreen + // Video size //////////////////////////////////////////////////////////////////////////*/ - //region Video size, resize, orientation, fullscreen + //region Video size + protected void setResizeMode(@AspectRatioFrameLayout.ResizeMode final int resizeMode) { binding.surfaceView.setResizeMode(resizeMode); binding.resizeTextView.setText(PlayerHelper.resizeTypeOf(context, resizeMode)); @@ -1569,6 +1581,7 @@ public abstract class VideoPlayerUi extends PlayerUi // Getters //////////////////////////////////////////////////////////////////////////*/ //region Getters + public PlayerBinding getBinding() { return binding; } From 1b39b5376f518ee570258da82fbe4fb09d11c231 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 15 Apr 2022 00:01:59 +0200 Subject: [PATCH 194/992] Add some javadocs; move preparing player uis to PlayerUiList --- .../org/schabi/newpipe/player/Player.java | 25 ++--- .../schabi/newpipe/player/ui/PlayerUi.java | 92 ++++++++++++++++++- .../newpipe/player/ui/PlayerUiList.java | 43 ++++++++- 3 files changed, 138 insertions(+), 22 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 b0fed3d7d..f8ea7bc90 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -447,7 +447,7 @@ public final class Player implements PlaybackListener, Listener { private void initUIsForCurrentPlayerType() { //noinspection SimplifyOptionalCallChains if (!UIs.get(NotificationPlayerUi.class).isPresent()) { - UIs.add(new NotificationPlayerUi(this)); + UIs.addAndPrepare(new NotificationPlayerUi(this)); } if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) @@ -469,24 +469,15 @@ public final class Player implements PlaybackListener, Listener { switch (playerType) { case MAIN: UIs.destroyAll(PopupPlayerUi.class); - UIs.add(new MainPlayerUi(this, binding)); + UIs.addAndPrepare(new MainPlayerUi(this, binding)); + break; + case POPUP: + UIs.destroyAll(MainPlayerUi.class); + UIs.addAndPrepare(new PopupPlayerUi(this, binding)); break; case AUDIO: UIs.destroyAll(VideoPlayerUi.class); break; - case POPUP: - UIs.destroyAll(MainPlayerUi.class); - UIs.add(new PopupPlayerUi(this, binding)); - break; - } - - if (fragmentListener != null) { - // make sure UIs know whether a service is connected or not - UIs.call(PlayerUi::onFragmentListenerSet); - } - if (!exoPlayerIsNull()) { - UIs.call(PlayerUi::initPlayer); - UIs.call(PlayerUi::initPlayback); } } @@ -1968,9 +1959,9 @@ public final class Player implements PlaybackListener, Listener { /*////////////////////////////////////////////////////////////////////////// - // Video size, resize, orientation, fullscreen + // Video size //////////////////////////////////////////////////////////////////////////*/ - //region Video size, resize, orientation, fullscreen + //region Video size @Override // exoplayer listener public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { if (DEBUG) { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 81e93ca23..c4db1f334 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -18,50 +18,105 @@ import org.schabi.newpipe.player.Player; import java.util.List; +/** + * A player UI is a component that can seamlessly connect and disconnect from the {@link Player} and + * provide a user interface of some sort. Try to extend this class instead of adding more code to + * {@link Player}! + */ public abstract class PlayerUi { - @NonNull protected Context context; - @NonNull protected Player player; + @NonNull protected final Context context; + @NonNull protected final Player player; + /** + * @param player the player instance that will be usable throughout the lifetime of this UI + */ public PlayerUi(@NonNull final Player player) { this.context = player.getContext(); this.player = player; } + /** + * @return the player instance this UI was constructed with + */ @NonNull public Player getPlayer() { return player; } + /** + * Called after the player received an intent and processed it + */ public void setupAfterIntent() { } + /** + * Called right after the exoplayer instance is constructed, or right after this UI is + * constructed if the exoplayer is already available then. Note that the exoplayer instance + * could be built and destroyed multiple times during the lifetime of the player, so this method + * might be called multiple times. + */ public void initPlayer() { } + /** + * Called when playback in the exoplayer is about to start, or right after this UI is + * constructed if the exoplayer and the play queue are already available then. The play queue + * will therefore always be not null. + */ public void initPlayback() { } + /** + * Called when the exoplayer instance is about to be destroyed. Note that the exoplayer instance + * could be built and destroyed multiple times during the lifetime of the player, so this method + * might be called multiple times. Be sure to unset any video surface view or play queue + * listeners! This will also be called when this UI is being discarded, just before {@link + * #destroy()}. + */ public void destroyPlayer() { } + /** + * Called when this UI is being discarded, either because the player is switching to a different + * UI or because the player is shutting down completely + */ public void destroy() { } + /** + * Called when the player is smooth-stopping, that is, transitioning smoothly to a new play + * queue after the user tapped on a new video stream while a stream was playing in the video + * detail fragment + */ public void smoothStopForImmediateReusing() { } + /** + * Called when the video detail fragment listener is connected with the player, or right after + * this UI is constructed if the listener is already connected then + */ public void onFragmentListenerSet() { } /** - * If you want to register new broadcast actions to receive here, add them to - * {@link Player#setupBroadcastReceiver()}. + * Broadcasts that the player receives will also be notified to UIs here. If you want to + * register new broadcast actions to receive here, add them to {@link + * Player#setupBroadcastReceiver()}. */ public void onBroadcastReceived(final Intent intent) { } + /** + * Called when stream progress (i.e. the current time in the seekbar) or stream duration change. + * Will surely be called every {@link Player#PROGRESS_LOOP_INTERVAL_MILLIS} while a stream is + * playing. + * @param currentProgress the current progress in milliseconds + * @param duration the duration of the stream being played + * @param bufferPercent the percentage of stream already buffered, see {@link + * com.google.android.exoplayer2.BasePlayer#getBufferedPercentage()} + */ public void onUpdateProgress(final int currentProgress, final int duration, final int bufferPercent) { @@ -97,27 +152,56 @@ public abstract class PlayerUi { public void onMuteUnmuteChanged(final boolean isMuted) { } + /** + * @see com.google.android.exoplayer2.Player.Listener#onTracksChanged(Tracks) + */ public void onTextTracksChanged(@NonNull final Tracks currentTracks) { } + /** + * @see com.google.android.exoplayer2.Player.Listener#onPlaybackParametersChanged + */ public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { } + /** + * @see com.google.android.exoplayer2.Player.Listener#onRenderedFirstFrame + */ public void onRenderedFirstFrame() { } + /** + * @see com.google.android.exoplayer2.text.TextOutput#onCues + */ public void onCues(@NonNull final List cues) { } + /** + * Called when the stream being played changes + * @param info the {@link StreamInfo} metadata object, along with data about the selected and + * available video streams (to be used to build the resolution menus, for example) + */ public void onMetadataChanged(@NonNull final StreamInfo info) { } + /** + * Called when the thumbnail for the current metadata was loaded + * @param bitmap the thumbnail to process, or null if there is no thumbnail or there was an + * error when loading the thumbnail + */ public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { } + /** + * Called when the play queue was edited: a stream was appended, moved or removed. + */ public void onPlayQueueEdited() { } + /** + * @param videoSize the new video size, useful to set the surface aspect ratio + * @see com.google.android.exoplayer2.Player.Listener#onVideoSizeChanged + */ public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java index 8c5c0dbfa..749cda02c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -8,10 +8,39 @@ import java.util.function.Consumer; public final class PlayerUiList { final List playerUis = new ArrayList<>(); - public void add(final PlayerUi playerUi) { + /** + * Adds the provided player ui to the list and calls on it the initialization functions that + * apply based on the current player state. The preparation step needs to be done since when UIs + * are removed and re-added, the player will not call e.g. initPlayer again since the exoplayer + * is already initialized, but we need to notify the newly built UI that the player is ready + * nonetheless. + * @param playerUi the player ui to prepare and add to the list; its {@link + * PlayerUi#getPlayer()} will be used to query information about the player + * state + */ + public void addAndPrepare(final PlayerUi playerUi) { + if (playerUi.getPlayer().getFragmentListener().isPresent()) { + // make sure UIs know whether a service is connected or not + playerUi.onFragmentListenerSet(); + } + + if (!playerUi.getPlayer().exoPlayerIsNull()) { + playerUi.initPlayer(); + if (playerUi.getPlayer().getPlayQueue() != null) { + playerUi.initPlayback(); + } + } + playerUis.add(playerUi); } + /** + * Destroys all matching player UIs and removes them from the list + * @param playerUiType the class of the player UI to destroy; the {@link + * Class#isInstance(Object)} method will be used, so even subclasses will be + * destroyed and removed + * @param the class type parameter + */ public void destroyAll(final Class playerUiType) { playerUis.stream() .filter(playerUiType::isInstance) @@ -22,6 +51,14 @@ public final class PlayerUiList { playerUis.removeIf(playerUiType::isInstance); } + /** + * @param playerUiType the class of the player UI to return; the {@link + * Class#isInstance(Object)} method will be used, so even subclasses could + * be returned + * @param the class type parameter + * @return the first player UI of the required type found in the list, or an empty {@link + * Optional} otherwise + */ public Optional get(final Class playerUiType) { return playerUis.stream() .filter(playerUiType::isInstance) @@ -29,6 +66,10 @@ public final class PlayerUiList { .findFirst(); } + /** + * Calls the provided consumer on all player UIs in the list + * @param consumer the consumer to call with player UIs + */ public void call(final Consumer consumer) { //noinspection SimplifyStreamApiCallChains playerUis.stream().forEach(consumer); From a19073ec011e7c314ccab2e9d84d466d235fd24a Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 12:03:59 +0200 Subject: [PATCH 195/992] Restore checkstyle and solve its errors --- app/build.gradle | 2 +- .../fragments/detail/VideoDetailFragment.java | 25 +++++++++---------- .../org/schabi/newpipe/player/Player.java | 6 +++-- .../player/notification/NotificationUtil.java | 4 +-- .../player/playback/PlayerMediaSession.java | 2 -- .../newpipe/player/ui/MainPlayerUi.java | 2 +- .../schabi/newpipe/player/ui/PlayerUi.java | 16 +++++++----- .../newpipe/player/ui/PlayerUiList.java | 4 +-- .../newpipe/player/ui/VideoPlayerUi.java | 15 ++++++----- .../custom/NotificationActionsPreference.java | 1 - 10 files changed, 41 insertions(+), 36 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 46eee8d00..9867037e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -166,7 +166,7 @@ afterEvaluate { if (!System.properties.containsKey('skipFormatKtlint')) { preDebugBuild.dependsOn formatKtlint } - //preDebugBuild.dependsOn runCheckstyle, runKtlint + preDebugBuild.dependsOn runCheckstyle, runKtlint } sonarqube { 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 cb8f0961f..8ffff2f9e 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 @@ -1,5 +1,16 @@ package org.schabi.newpipe.fragments.detail; +import static android.text.TextUtils.isEmpty; +import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; +import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; +import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; +import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; +import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired; +import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET; +import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; +import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams; + import android.animation.ValueAnimator; import android.app.Activity; import android.content.BroadcastReceiver; @@ -43,7 +54,6 @@ import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; -import androidx.viewbinding.ViewBinding; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; @@ -78,9 +88,9 @@ import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.PlayerService.PlayerType; -import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.helper.PlayerHelper; @@ -118,17 +128,6 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; -import static android.text.TextUtils.isEmpty; -import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; -import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; -import static org.schabi.newpipe.ktx.ViewUtils.animate; -import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; -import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; -import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired; -import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET; -import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView; -import static org.schabi.newpipe.util.ListHelper.getUrlAndNonTorrentStreams; - public final class VideoDetailFragment extends BaseStateFragment implements BackPressable, 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 f8ea7bc90..0755f9b4d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -239,6 +239,7 @@ public final class Player implements PlaybackListener, Listener { // UIs, listeners and disposables //////////////////////////////////////////////////////////////////////////*/ + @SuppressWarnings("MemberName") // keep the unusual member name private final PlayerUiList UIs = new PlayerUiList(); private BroadcastReceiver broadcastReceiver; @@ -1148,7 +1149,7 @@ public final class Player implements PlaybackListener, Listener { simpleExoPlayer.setRepeatMode(repeatMode); } } - + public void cycleNextRepeatMode() { setRepeatMode(nextRepeatMode(getRepeatMode())); } @@ -1181,7 +1182,7 @@ public final class Player implements PlaybackListener, Listener { UIs.call(playerUi -> playerUi.onShuffleModeEnabledChanged(shuffleModeEnabled)); notifyPlaybackUpdateToListeners(); } - + public void toggleShuffleModeEnabled() { if (!exoPlayerIsNull()) { simpleExoPlayer.setShuffleModeEnabled(!simpleExoPlayer.getShuffleModeEnabled()); @@ -2301,6 +2302,7 @@ public final class Player implements PlaybackListener, Listener { /** * @return the user interfaces connected with the player */ + @SuppressWarnings("MethodName") // keep the unusual method name public PlayerUiList UIs() { return UIs; } diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 5f0052453..28c3b3655 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -132,7 +132,7 @@ public final class NotificationUtil { // also update content intent, in case the user switched players notificationBuilder.setContentIntent(PendingIntent.getActivity(player.getContext(), - NOTIFICATION_ID, getIntentForNotification(player), FLAG_UPDATE_CURRENT)); + NOTIFICATION_ID, getIntentForNotification(), FLAG_UPDATE_CURRENT)); notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); @@ -321,7 +321,7 @@ public final class NotificationUtil { new Intent(intentAction), FLAG_UPDATE_CURRENT)); } - private Intent getIntentForNotification(final Player player) { + private Intent getIntentForNotification() { if (player.audioPlayerSelected() || player.popupPlayerSelected()) { // Means we play in popup or audio only. Let's show the play queue return NavigationHelper.getPlayQueueActivityIntent(player.getContext()); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java index 2f261a0fa..3be9b6173 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java @@ -10,8 +10,6 @@ import org.schabi.newpipe.player.mediasession.MediaSessionCallback; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.ui.VideoPlayerUi; -import java.util.Optional; - public class PlayerMediaSession implements MediaSessionCallback { private final Player player; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index c62382782..3bdda0029 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -497,7 +497,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh //region Captions (text tracks) @Override - protected void setupSubtitleView(float captionScale) { + protected void setupSubtitleView(final float captionScale) { final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); final int minimumLength = Math.min(metrics.heightPixels, metrics.widthPixels); final float captionRatioInverse = 20f + 4f * (1.0f - captionScale); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index c4db1f334..499800690 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -46,7 +46,7 @@ public abstract class PlayerUi { /** - * Called after the player received an intent and processed it + * Called after the player received an intent and processed it. */ public void setupAfterIntent() { } @@ -80,7 +80,7 @@ public abstract class PlayerUi { /** * Called when this UI is being discarded, either because the player is switching to a different - * UI or because the player is shutting down completely + * UI or because the player is shutting down completely. */ public void destroy() { } @@ -88,14 +88,14 @@ public abstract class PlayerUi { /** * Called when the player is smooth-stopping, that is, transitioning smoothly to a new play * queue after the user tapped on a new video stream while a stream was playing in the video - * detail fragment + * detail fragment. */ public void smoothStopForImmediateReusing() { } /** * Called when the video detail fragment listener is connected with the player, or right after - * this UI is constructed if the listener is already connected then + * this UI is constructed if the listener is already connected then. */ public void onFragmentListenerSet() { } @@ -104,6 +104,7 @@ public abstract class PlayerUi { * Broadcasts that the player receives will also be notified to UIs here. If you want to * register new broadcast actions to receive here, add them to {@link * Player#setupBroadcastReceiver()}. + * @param intent the broadcast intent received by the player */ public void onBroadcastReceived(final Intent intent) { } @@ -154,12 +155,14 @@ public abstract class PlayerUi { /** * @see com.google.android.exoplayer2.Player.Listener#onTracksChanged(Tracks) + * @param currentTracks the available tracks information */ public void onTextTracksChanged(@NonNull final Tracks currentTracks) { } /** * @see com.google.android.exoplayer2.Player.Listener#onPlaybackParametersChanged + * @param playbackParameters the new playback parameters */ public void onPlaybackParametersChanged(@NonNull final PlaybackParameters playbackParameters) { } @@ -172,12 +175,13 @@ public abstract class PlayerUi { /** * @see com.google.android.exoplayer2.text.TextOutput#onCues + * @param cues the cues to pass to the subtitle view */ public void onCues(@NonNull final List cues) { } /** - * Called when the stream being played changes + * Called when the stream being played changes. * @param info the {@link StreamInfo} metadata object, along with data about the selected and * available video streams (to be used to build the resolution menus, for example) */ @@ -185,7 +189,7 @@ public abstract class PlayerUi { } /** - * Called when the thumbnail for the current metadata was loaded + * Called when the thumbnail for the current metadata was loaded. * @param bitmap the thumbnail to process, or null if there is no thumbnail or there was an * error when loading the thumbnail */ diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java index 749cda02c..05c0ed5b3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -35,7 +35,7 @@ public final class PlayerUiList { } /** - * Destroys all matching player UIs and removes them from the list + * Destroys all matching player UIs and removes them from the list. * @param playerUiType the class of the player UI to destroy; the {@link * Class#isInstance(Object)} method will be used, so even subclasses will be * destroyed and removed @@ -67,7 +67,7 @@ public final class PlayerUiList { } /** - * Calls the provided consumer on all player UIs in the list + * Calls the provided consumer on all player UIs in the list. * @param consumer the consumer to call with player UIs */ public void call(final Consumer consumer) { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index f4ebc3304..393bf141b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -86,7 +86,8 @@ import java.util.Optional; import java.util.stream.Collectors; public abstract class VideoPlayerUi extends PlayerUi - implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { + implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, View.OnLongClickListener, + PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener { private static final String TAG = VideoPlayerUi.class.getSimpleName(); // time constants @@ -476,7 +477,7 @@ public abstract class VideoPlayerUi extends PlayerUi binding.endScreen.setImageBitmap(endScreenBitmap); } - protected abstract float calculateMaxEndScreenThumbnailHeight(@NonNull final Bitmap bitmap); + protected abstract float calculateMaxEndScreenThumbnailHeight(@NonNull Bitmap bitmap); //endregion @@ -511,6 +512,7 @@ public abstract class VideoPlayerUi extends PlayerUi /** * Sets the current duration into the corresponding elements. + * @param currentProgress the current progress, in milliseconds */ private void updatePlayBackElementsCurrentDuration(final int currentProgress) { // Don't set seekbar progress while user is seeking @@ -522,6 +524,7 @@ public abstract class VideoPlayerUi extends PlayerUi /** * Sets the video duration time into all control components (e.g. seekbar). + * @param duration the video duration, in milliseconds */ private void setVideoDurationToControls(final int duration) { binding.playbackEndTime.setText(getTimeString(duration)); @@ -1214,7 +1217,7 @@ public abstract class VideoPlayerUi extends PlayerUi final MediaItemTag.Quality quality = currentMetadata.getMaybeQuality().get(); final List availableStreams = quality.getSortedVideoStreams(); final int selectedStreamIndex = quality.getSelectedVideoStreamIndex(); - if (selectedStreamIndex == menuItemIndex|| availableStreams.size() <= menuItemIndex) { + if (selectedStreamIndex == menuItemIndex || availableStreams.size() <= menuItemIndex) { return true; } @@ -1320,7 +1323,7 @@ public abstract class VideoPlayerUi extends PlayerUi } @Override - public void onCues(@NonNull List cues) { + public void onCues(@NonNull final List cues) { super.onCues(cues); binding.subtitleView.setCues(cues); } @@ -1332,7 +1335,7 @@ public abstract class VideoPlayerUi extends PlayerUi binding.subtitleView.setStyle(captionStyle); } - protected abstract void setupSubtitleView(final float captionScale); + protected abstract void setupSubtitleView(float captionScale); //endregion @@ -1518,7 +1521,7 @@ public abstract class VideoPlayerUi extends PlayerUi } @Override - public void onVideoSizeChanged(@NonNull VideoSize videoSize) { + public void onVideoSizeChanged(@NonNull final VideoSize videoSize) { super.onVideoSizeChanged(videoSize); binding.surfaceView.setAspectRatio(((float) videoSize.width) / videoSize.height); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index dfcf2e597..b4f6d598a 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -28,7 +28,6 @@ import androidx.preference.PreferenceViewHolder; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; -import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.notification.NotificationConstants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; From 4979f84e4116d114dca851a31d706bec90a93450 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 16 Apr 2022 16:01:23 +0200 Subject: [PATCH 196/992] Solve some Sonarlint warnings --- .../newpipe/local/dialog/PlaylistDialog.java | 37 ++++++++++++++-- .../newpipe/player/PlayQueueActivity.java | 8 ++-- .../org/schabi/newpipe/player/Player.java | 42 +------------------ .../schabi/newpipe/player/PlayerService.java | 17 ++++---- .../gesture/BasePlayerGestureListener.kt | 5 ++- .../player/notification/NotificationUtil.java | 1 + .../newpipe/player/ui/MainPlayerUi.java | 13 +++--- .../schabi/newpipe/player/ui/PlayerUi.java | 2 +- .../newpipe/player/ui/PopupPlayerUi.java | 16 ++++--- .../newpipe/player/ui/VideoPlayerUi.java | 40 +++++++++--------- 10 files changed, 86 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java index f568ef81a..dec8b05b2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java @@ -9,15 +9,20 @@ import android.view.Window; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; +import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.StateSaver; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.disposables.Disposable; @@ -131,13 +136,13 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave * @param context context used for accessing the database * @param streamEntities used for crating the dialog * @param onExec execution that should occur after a dialog got created, e.g. showing it - * @return Disposable + * @return the disposable that was created */ public static Disposable createCorrespondingDialog( final Context context, final List streamEntities, - final Consumer onExec - ) { + final Consumer onExec) { + return new LocalPlaylistManager(NewPipeDatabase.getInstance(context)) .hasPlaylists() .observeOn(AndroidSchedulers.mainThread()) @@ -147,4 +152,30 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave : PlaylistCreationDialog.newInstance(streamEntities)) ); } + + /** + * Creates a {@link PlaylistAppendDialog} when playlists exists, + * otherwise a {@link PlaylistCreationDialog}. If the player's play queue is null or empty, no + * dialog will be created. + * + * @param player the player from which to extract the context and the play queue + * @param fragmentManager the fragment manager to use to show the dialog + * @return the disposable that was created + */ + public static Disposable showForPlayQueue( + final Player player, + @NonNull final FragmentManager fragmentManager) { + + final List streamEntities = Stream.of(player.getPlayQueue()) + .filter(Objects::nonNull) + .flatMap(playQueue -> Objects.requireNonNull(playQueue).getStreams().stream()) + .map(StreamEntity::new) + .collect(Collectors.toList()); + if (streamEntities.isEmpty()) { + return Disposable.empty(); + } + + return PlaylistDialog.createCorrespondingDialog(player.getContext(), streamEntities, + dialog -> dialog.show(fragmentManager, "PlaylistDialog")); + } } 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 cdba900f9..c18a7f487 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java @@ -29,6 +29,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; +import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.helper.PlaybackParameterDialog; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -53,8 +54,6 @@ public final class PlayQueueActivity extends AppCompatActivity private Player player; - private PlayQueueAdapter adapter = null; - private boolean serviceBound; private ServiceConnection serviceConnection; @@ -128,7 +127,7 @@ public final class PlayQueueActivity extends AppCompatActivity NavigationHelper.openSettings(this); return true; case R.id.action_append_playlist: - player.onAddToPlaylistClicked(getSupportFragmentManager()); + PlaylistDialog.showForPlayQueue(player, getSupportFragmentManager()); return true; case R.id.action_playback_speed: openPlaybackParameterDialog(); @@ -441,10 +440,9 @@ public final class PlayQueueActivity extends AppCompatActivity @Override public void onQueueUpdate(@Nullable final PlayQueue queue) { if (queue == null) { - adapter = null; queueControlBinding.playQueue.setAdapter(null); } else { - adapter = new PlayQueueAdapter(this, queue); + final PlayQueueAdapter adapter = new PlayQueueAdapter(this, queue); adapter.setSelectedListener(getOnSelectedListener()); queueControlBinding.playQueue.setAdapter(adapter); } 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 0755f9b4d..2d44c6449 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -63,16 +63,6 @@ import android.view.LayoutInflater; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatImageButton; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.content.ContextCompat; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.C; @@ -96,7 +86,6 @@ import com.squareup.picasso.Target; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; @@ -105,7 +94,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; -import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.event.PlayerEventListener; @@ -116,6 +104,7 @@ import org.schabi.newpipe.player.helper.MediaSessionManager; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.mediaitem.MediaItemTag; +import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlayerMediaSession; @@ -125,7 +114,6 @@ import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver.SourceType; import org.schabi.newpipe.player.ui.MainPlayerUi; -import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.player.ui.PlayerUiList; import org.schabi.newpipe.player.ui.PopupPlayerUi; @@ -137,10 +125,8 @@ import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.SerializedCache; import org.schabi.newpipe.util.StreamTypeUtil; -import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import java.util.stream.IntStream; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -1192,32 +1178,6 @@ public final class Player implements PlaybackListener, Listener { - /*////////////////////////////////////////////////////////////////////////// - // Playlist append TODO this does not make sense here - //////////////////////////////////////////////////////////////////////////*/ - //region Playlist append - - public void onAddToPlaylistClicked(@NonNull final FragmentManager fragmentManager) { - if (DEBUG) { - Log.d(TAG, "onAddToPlaylistClicked() called"); - } - - if (getPlayQueue() != null) { - PlaylistDialog.createCorrespondingDialog( - getContext(), - getPlayQueue() - .getStreams() - .stream() - .map(StreamEntity::new) - .collect(Collectors.toList()), - dialog -> dialog.show(fragmentManager, TAG) - ); - } - } - //endregion - - - /*////////////////////////////////////////////////////////////////////////// // Mute / Unmute //////////////////////////////////////////////////////////////////////////*/ 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 b5014eeed..326b01590 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -71,16 +71,17 @@ public final class PlayerService extends Service { Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]"); } - if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) - && player.getPlayQueue() == null) { - // Player is not working, no need to process media button's action - return START_NOT_STICKY; + + if (!Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) + || player.getPlayQueue() != null) { + // ^ no need to process media button's action if player is not working + + player.handleIntent(intent); + if (player.getMediaSessionManager() != null) { + player.getMediaSessionManager().handleMediaButtonIntent(intent); + } } - player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); - } return START_NOT_STICKY; } diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt index bd5d6f1c5..b006e73aa 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -92,7 +92,10 @@ abstract class BasePlayerGestureListener( return true } - return if (onDownNotDoubleTapping(e)) super.onDown(e) else true + if (onDownNotDoubleTapping(e)) { + return super.onDown(e) + } + return true } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 28c3b3655..2ba754500 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -266,6 +266,7 @@ public final class NotificationUtil { null); } + // fallthrough case NotificationConstants.PLAY_PAUSE: if (player.getCurrentState() == Player.STATE_COMPLETED) { return getAction(R.drawable.ic_replay, diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 3bdda0029..eebcc81c4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -51,6 +51,7 @@ import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.info_list.StreamSegmentAdapter; import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.gesture.BasePlayerGestureListener; @@ -147,7 +148,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.addToPlaylistButton.setOnClickListener(v -> getParentActivity().map(FragmentActivity::getSupportFragmentManager) - .ifPresent(player::onAddToPlaylistClicked)); + .ifPresent(fragmentManager -> + PlaylistDialog.showForPlayQueue(player, fragmentManager))); settingsContentObserver = new ContentObserver(new Handler()) { @Override @@ -401,6 +403,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh //////////////////////////////////////////////////////////////////////////*/ //region Controls showing / hiding + @Override protected void showOrHideButtons() { super.showOrHideButtons(); @Nullable final PlayQueue playQueue = player.getPlayQueue(); @@ -667,12 +670,11 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } animate(binding.itemsListPanel, false, DEFAULT_CONTROLS_DURATION, - AnimationType.SLIDE_AND_ALPHA, 0, () -> { + AnimationType.SLIDE_AND_ALPHA, 0, () -> // Even when queueLayout is GONE it receives touch events // and ruins normal behavior of the app. This line fixes it binding.itemsListPanel.setTranslationY( - -binding.itemsListPanel.getHeight() * 5); - }); + -binding.itemsListPanel.getHeight() * 5.0f)); // clear focus, otherwise a white rectangle remains on top of the player binding.itemsListClose.clearFocus(); @@ -845,8 +847,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), player.getPlaybackPitch(), - player.getPlaybackSkipSilence(), (speed, pitch, skipSilence) - -> player.setPlaybackParameters(speed, pitch, skipSilence)) + player.getPlaybackSkipSilence(), player::setPlaybackParameters) .show(activity.getSupportFragmentManager(), null); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 499800690..9ce04bfd5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -31,7 +31,7 @@ public abstract class PlayerUi { /** * @param player the player instance that will be usable throughout the lifetime of this UI */ - public PlayerUi(@NonNull final Player player) { + protected PlayerUi(@NonNull final Player player) { this.context = player.getContext(); this.player = player; } 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 43440b873..8283437f8 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 @@ -1,5 +1,6 @@ package org.schabi.newpipe.player.ui; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; @@ -140,8 +141,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { binding.segmentsButton.setVisibility(View.GONE); binding.moreOptionsButton.setVisibility(View.GONE); binding.topControls.setOrientation(LinearLayout.HORIZONTAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.WRAP_CONTENT; + binding.primaryControls.getLayoutParams().width = WRAP_CONTENT; binding.secondaryControls.setAlpha(1.0f); binding.secondaryControls.setVisibility(View.VISIBLE); binding.secondaryControls.setTranslationY(0); @@ -193,14 +193,12 @@ public final class PopupPlayerUi extends VideoPlayerUi { updateScreenSize(); changePopupSize(popupLayoutParams.width); checkPopupPositionBounds(); - } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { - // Use only audio source when screen turns off while popup player is playing - if (player.isPlaying() || player.isLoading()) { + } else if (player.isPlaying() || player.isLoading()) { + if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + // Use only audio source when screen turns off while popup player is playing player.useVideoSource(false); - } - } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { - // Restore video source when screen turns on and user is watching video in popup player - if (player.isPlaying() || player.isLoading()) { + } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + // Restore video source when screen turns on and user was watching video in popup player.useVideoSource(true); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 393bf141b..5b0be6f64 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -41,7 +41,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatImageButton; import androidx.appcompat.widget.PopupMenu; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; @@ -142,7 +141,7 @@ public abstract class VideoPlayerUi extends PlayerUi //////////////////////////////////////////////////////////////////////////*/ //region Constructor, setup, destroy - public VideoPlayerUi(@NonNull final Player player, + protected VideoPlayerUi(@NonNull final Player player, @NonNull final PlayerBinding playerBinding) { super(player); binding = playerBinding; @@ -912,7 +911,20 @@ public abstract class VideoPlayerUi extends PlayerUi @Override public void onRepeatModeChanged(@RepeatMode final int repeatMode) { super.onRepeatModeChanged(repeatMode); - setRepeatModeButton(binding.repeatButton, repeatMode); + + switch (repeatMode) { + case REPEAT_MODE_OFF: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_off); + break; + case REPEAT_MODE_ONE: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_one); + break; + case REPEAT_MODE_ALL: + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_all); + break; + default: + break; // unreachable + } } @Override @@ -927,21 +939,6 @@ public abstract class VideoPlayerUi extends PlayerUi setMuteButton(isMuted); } - private void setRepeatModeButton(final AppCompatImageButton imageButton, - @RepeatMode final int repeatMode) { - switch (repeatMode) { - case REPEAT_MODE_OFF: - imageButton.setImageResource(R.drawable.exo_controls_repeat_off); - break; - case REPEAT_MODE_ONE: - imageButton.setImageResource(R.drawable.exo_controls_repeat_one); - break; - case REPEAT_MODE_ALL: - imageButton.setImageResource(R.drawable.exo_controls_repeat_all); - break; - } - } - private void setMuteButton(final boolean isMuted) { binding.switchMute.setImageDrawable(AppCompatResources.getDrawable(context, isMuted ? R.drawable.ic_volume_off : R.drawable.ic_volume_up)); @@ -1037,6 +1034,7 @@ public abstract class VideoPlayerUi extends PlayerUi binding.qualityTextView.setVisibility(View.VISIBLE); binding.surfaceView.setVisibility(View.VISIBLE); + // fallthrough default: binding.endScreen.setVisibility(View.GONE); binding.playbackEndTime.setVisibility(View.VISIBLE); @@ -1426,8 +1424,6 @@ public abstract class VideoPlayerUi extends PlayerUi public boolean onKeyDown(final int keyCode) { switch (keyCode) { - default: - break; case KeyEvent.KEYCODE_BACK: if (DeviceUtils.isTv(context) && isControlsVisible()) { hideControls(0, 0); @@ -1442,7 +1438,7 @@ public abstract class VideoPlayerUi extends PlayerUi if ((binding.getRoot().hasFocus() && !binding.playbackControlRoot.hasFocus()) || isAnyListViewOpen()) { // do not interfere with focus in playlist and play queue etc. - return false; + break; } if (player.getCurrentState() == org.schabi.newpipe.player.Player.STATE_BLOCKED) { @@ -1458,6 +1454,8 @@ public abstract class VideoPlayerUi extends PlayerUi return true; } break; + default: + break; // ignore other keys } return false; From 1cf746f7216c173194e17b52abb7bac85763cb23 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 7 Jul 2022 11:09:07 +0200 Subject: [PATCH 197/992] Fix volume gestures not working anymore --- .../gesture/MainPlayerGestureListener.kt | 55 ++++++++++++------- .../newpipe/player/ui/MainPlayerUi.java | 7 ++- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index 81e216006..fd7b4ecf0 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -8,11 +8,13 @@ import android.view.View.OnTouchListener import android.widget.ProgressBar import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources +import androidx.core.view.isVisible import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate import org.schabi.newpipe.player.Player +import org.schabi.newpipe.player.helper.AudioReactor import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.MainPlayerUi import kotlin.math.abs @@ -64,22 +66,27 @@ class MainPlayerGestureListener( } private fun onScrollVolume(distanceY: Float) { + val bar: ProgressBar = binding.volumeProgressBar + val audioReactor: AudioReactor = player.audioReactor + // If we just started sliding, change the progress bar to match the system volume - if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { - val volumePercent: Float = - player.audioReactor.volume / player.audioReactor.maxVolume.toFloat() - binding.volumeProgressBar.progress = (volumePercent * MAX_GESTURE_LENGTH).toInt() + if (!binding.volumeRelativeLayout.isVisible) { + val volumePercent: Float = audioReactor.volume / audioReactor.maxVolume.toFloat() + bar.progress = (volumePercent * bar.max).toInt() } + // Update progress bar binding.volumeProgressBar.incrementProgressBy(distanceY.toInt()) - val currentProgressPercent: Float = - binding.volumeProgressBar.progress.toFloat() / MAX_GESTURE_LENGTH - val currentVolume = (player.audioReactor.maxVolume * currentProgressPercent).toInt() - player.audioReactor.volume = currentVolume + + // Update volume + val currentProgressPercent: Float = bar.progress / bar.max.toFloat() + val currentVolume = (audioReactor.maxVolume * currentProgressPercent).toInt() + audioReactor.volume = currentVolume if (DEBUG) { Log.d(TAG, "onScroll().volumeControl, currentVolume = $currentVolume") } + // Update player center image binding.volumeImageView.setImageDrawable( AppCompatResources.getDrawable( player.context, @@ -92,12 +99,11 @@ class MainPlayerGestureListener( ) ) - if (binding.volumeRelativeLayout.visibility != View.VISIBLE) { + // Make sure the correct layout is visible + if (!binding.volumeRelativeLayout.isVisible) { binding.volumeRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) } - if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { - binding.volumeRelativeLayout.visibility = View.GONE - } + binding.brightnessRelativeLayout.isVisible = false } private fun onScrollBrightness(distanceY: Float) { @@ -105,9 +111,13 @@ class MainPlayerGestureListener( val window = parent.window val layoutParams = window.attributes val bar: ProgressBar = binding.brightnessProgressBar + + // Update progress bar val oldBrightness = layoutParams.screenBrightness bar.progress = (bar.max * max(0f, min(1f, oldBrightness))).toInt() bar.incrementProgressBy(distanceY.toInt()) + + // Update brightness val currentProgressPercent = bar.progress.toFloat() / bar.max layoutParams.screenBrightness = currentProgressPercent window.attributes = layoutParams @@ -121,26 +131,32 @@ class MainPlayerGestureListener( "currentBrightness = " + currentProgressPercent ) } + + // Update player center image binding.brightnessImageView.setImageDrawable( AppCompatResources.getDrawable( player.context, - if (currentProgressPercent < 0.25) R.drawable.ic_brightness_low else if (currentProgressPercent < 0.75) R.drawable.ic_brightness_medium else R.drawable.ic_brightness_high + when { + currentProgressPercent < 0.25 -> R.drawable.ic_brightness_low + currentProgressPercent < 0.75 -> R.drawable.ic_brightness_medium + else -> R.drawable.ic_brightness_high + } ) ) - if (binding.brightnessRelativeLayout.visibility != View.VISIBLE) { + + // Make sure the correct layout is visible + if (!binding.brightnessRelativeLayout.isVisible) { binding.brightnessRelativeLayout.animate(true, 200, AnimationType.SCALE_AND_ALPHA) } - if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { - binding.volumeRelativeLayout.visibility = View.GONE - } + binding.volumeRelativeLayout.isVisible = false } override fun onScrollEnd(event: MotionEvent) { super.onScrollEnd(event) - if (binding.volumeRelativeLayout.visibility == View.VISIBLE) { + if (binding.volumeRelativeLayout.isVisible) { binding.volumeRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) } - if (binding.brightnessRelativeLayout.visibility == View.VISIBLE) { + if (binding.brightnessRelativeLayout.isVisible) { binding.brightnessRelativeLayout.animate(false, 200, AnimationType.SCALE_AND_ALPHA, 200) } } @@ -210,7 +226,6 @@ class MainPlayerGestureListener( private val TAG = MainPlayerGestureListener::class.java.simpleName private val DEBUG = MainActivity.DEBUG private const val MOVEMENT_THRESHOLD = 40 - const val MAX_GESTURE_LENGTH = 0.75f private fun getNavigationBarHeight(context: Context): Int { val resId = context.resources diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index eebcc81c4..d9f5ea7f4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -520,12 +520,13 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh public void onLayoutChange(final View view, final int l, final int t, final int r, final int b, final int ol, final int ot, final int or, final int ob) { if (l != ol || t != ot || r != or || b != ob) { - // Use smaller value to be consistent between screen orientations - // (and to make usage easier) + // Use a smaller value to be consistent across screen orientations, and to make usage + // easier. Multiply by 3/4 to ensure the user does not need to move the finger up to the + // screen border, in order to reach the maximum volume/brightness. final int width = r - l; final int height = b - t; final int min = Math.min(width, height); - final int maxGestureLength = (int) (min * MainPlayerGestureListener.MAX_GESTURE_LENGTH); + final int maxGestureLength = (int) (min * 0.75); if (DEBUG) { Log.d(TAG, "maxGestureLength = " + maxGestureLength); From 9c51fc3adeaac4670da0bead156315b286f8b5c3 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 7 Jul 2022 11:59:00 +0200 Subject: [PATCH 198/992] Move functions to get Android dimen to ThemeHelper --- .../gesture/MainPlayerGestureListener.kt | 28 ++++++------------- .../org/schabi/newpipe/util/ThemeHelper.java | 16 +++++++++++ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index fd7b4ecf0..095b3ccdb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -1,6 +1,5 @@ package org.schabi.newpipe.player.gesture -import android.content.Context import android.util.Log import android.view.MotionEvent import android.view.View @@ -17,6 +16,7 @@ import org.schabi.newpipe.player.Player import org.schabi.newpipe.player.helper.AudioReactor import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.MainPlayerUi +import org.schabi.newpipe.util.ThemeHelper.getAndroidDimenPx import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -172,9 +172,13 @@ class MainPlayerGestureListener( return false } - val isTouchingStatusBar: Boolean = initialEvent.y < getStatusBarHeight(player.context) - val isTouchingNavigationBar: Boolean = - initialEvent.y > (binding.root.height - getNavigationBarHeight(player.context)) + // Calculate heights of status and navigation bars + val statusBarHeight = getAndroidDimenPx(player.context, "status_bar_height") + val navigationBarHeight = getAndroidDimenPx(player.context, "navigation_bar_height") + + // Do not handle this event if initially it started from status or navigation bars + val isTouchingStatusBar = initialEvent.y < statusBarHeight + val isTouchingNavigationBar = initialEvent.y > (binding.root.height - navigationBarHeight) if (isTouchingStatusBar || isTouchingNavigationBar) { return false } @@ -226,21 +230,5 @@ class MainPlayerGestureListener( private val TAG = MainPlayerGestureListener::class.java.simpleName private val DEBUG = MainActivity.DEBUG private const val MOVEMENT_THRESHOLD = 40 - - private fun getNavigationBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("navigation_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } - - private fun getStatusBarHeight(context: Context): Int { - val resId = context.resources - .getIdentifier("status_bar_height", "dimen", "android") - return if (resId > 0) { - context.resources.getDimensionPixelSize(resId) - } else 0 - } } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index b8e3a86ed..389af80ee 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -244,6 +244,22 @@ public final class ThemeHelper { return AppCompatResources.getDrawable(context, typedValue.resourceId); } + /** + * Gets a runtime dimen from the {@code android} package. Should be used for dimens for which + * normal accessing with {@code R.dimen.} is not available. + * + * @param context context + * @param name dimen resource name (e.g. navigation_bar_height) + * @return the obtained dimension, in pixels, or 0 if the resource could not be resolved + */ + public static int getAndroidDimenPx(@NonNull final Context context, final String name) { + final int resId = context.getResources().getIdentifier(name, "dimen", "android"); + if (resId <= 0) { + return 0; + } + return context.getResources().getDimensionPixelSize(resId); + } + private static String getSelectedThemeKey(final Context context) { final String themeKey = context.getString(R.string.theme_key); final String defaultTheme = context.getResources().getString(R.string.default_theme_value); From 3692858a3d10fb33d0386149d630264ca93eca23 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 8 Jul 2022 22:33:35 +0200 Subject: [PATCH 199/992] Move popup layout param to PopupPlayerUi --- .../gesture/PopupPlayerGestureListener.kt | 71 +++++------ .../newpipe/player/helper/PlayerHelper.java | 103 +--------------- .../newpipe/player/ui/PopupPlayerUi.java | 110 +++++++++++++++++- 3 files changed, 144 insertions(+), 140 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt index b8c1bc54c..bda6ee8d1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -7,7 +7,6 @@ import android.view.ViewConfiguration import org.schabi.newpipe.MainActivity import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate -import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.PopupPlayerUi import kotlin.math.abs import kotlin.math.hypot @@ -87,7 +86,7 @@ class PopupPlayerGestureListener( player.changeState(player.currentState) } if (!playerUi.isPopupClosing) { - PlayerHelper.savePopupPositionAndSizeToPrefs(playerUi) + playerUi.savePopupPositionAndSizeToPrefs() } } @@ -106,40 +105,42 @@ class PopupPlayerGestureListener( } private fun handleMultiDrag(event: MotionEvent): Boolean { - if (initPointerDistance != -1.0 && event.pointerCount == 2) { - // get the movements of the fingers - val firstPointerMove = hypot( - event.getX(0) - initFirstPointerX.toDouble(), - event.getY(0) - initFirstPointerY.toDouble() - ) - val secPointerMove = hypot( - event.getX(1) - initSecPointerX.toDouble(), - event.getY(1) - initSecPointerY.toDouble() - ) - - // minimum threshold beyond which pinch gesture will work - val minimumMove = ViewConfiguration.get(player.context).scaledTouchSlop - - if (max(firstPointerMove, secPointerMove) > minimumMove) { - // calculate current distance between the pointers - val currentPointerDistance = hypot( - event.getX(0) - event.getX(1).toDouble(), - event.getY(0) - event.getY(1).toDouble() - ) - - val popupWidth = playerUi.popupLayoutParams.width.toDouble() - // change co-ordinates of popup so the center stays at the same position - val newWidth = popupWidth * currentPointerDistance / initPointerDistance - initPointerDistance = currentPointerDistance - playerUi.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt() - - playerUi.checkPopupPositionBounds() - playerUi.updateScreenSize() - playerUi.changePopupSize(min(playerUi.screenWidth.toDouble(), newWidth).toInt()) - return true - } + if (initPointerDistance == -1.0 || event.pointerCount != 2) { + return false } - return false + + // get the movements of the fingers + val firstPointerMove = hypot( + event.getX(0) - initFirstPointerX.toDouble(), + event.getY(0) - initFirstPointerY.toDouble() + ) + val secPointerMove = hypot( + event.getX(1) - initSecPointerX.toDouble(), + event.getY(1) - initSecPointerY.toDouble() + ) + + // minimum threshold beyond which pinch gesture will work + val minimumMove = ViewConfiguration.get(player.context).scaledTouchSlop + if (max(firstPointerMove, secPointerMove) <= minimumMove) { + return false + } + + // calculate current distance between the pointers + val currentPointerDistance = hypot( + event.getX(0) - event.getX(1).toDouble(), + event.getY(0) - event.getY(1).toDouble() + ) + + val popupWidth = playerUi.popupLayoutParams.width.toDouble() + // change co-ordinates of popup so the center stays at the same position + val newWidth = popupWidth * currentPointerDistance / initPointerDistance + initPointerDistance = currentPointerDistance + playerUi.popupLayoutParams.x += ((popupWidth - newWidth) / 2.0).toInt() + + playerUi.checkPopupPositionBounds() + playerUi.updateScreenSize() + playerUi.changePopupSize(min(playerUi.screenWidth.toDouble(), newWidth).toInt()) + return true } private fun onPopupResizingStart() { diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index ec4cf8602..d1d29dd71 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -10,19 +10,13 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLA import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_BACKGROUND; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_NONE; import static org.schabi.newpipe.player.helper.PlayerHelper.MinimizeMode.MINIMIZE_ON_EXIT_MODE_POPUP; -import static org.schabi.newpipe.player.ui.PopupPlayerUi.IDLE_WINDOW_FLAGS; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.graphics.PixelFormat; -import android.os.Build; import android.provider.Settings; -import android.view.Gravity; -import android.view.ViewGroup; -import android.view.WindowManager; import android.view.accessibility.CaptioningManager; import androidx.annotation.IntDef; @@ -49,12 +43,11 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.utils.Utils; -import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; -import org.schabi.newpipe.player.ui.PopupPlayerUi; import org.schabi.newpipe.util.ListHelper; import java.lang.annotation.Retention; @@ -77,20 +70,6 @@ public final class PlayerHelper { private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x"); private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%"); - /** - * Maximum opacity allowed for Android 12 and higher to allow touches on other apps when using - * NewPipe's popup player. - * - *

- * This value is hardcoded instead of being get dynamically with the method linked of the - * constant documentation below, because it is not static and popup player layout parameters - * are generated with static methods. - *

- * - * @see WindowManager.LayoutParams#FLAG_NOT_TOUCHABLE - */ - private static final float MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER = 0.8f; - @Retention(SOURCE) @IntDef({AUTOPLAY_TYPE_ALWAYS, AUTOPLAY_TYPE_WIFI, AUTOPLAY_TYPE_NEVER}) @@ -525,90 +504,10 @@ public final class PlayerHelper { .apply(); } - /** - * @param playerUi {@code screenWidth} and {@code screenHeight} must have been initialized - * @return the popup starting layout params - */ - @SuppressLint("RtlHardcoded") - public static WindowManager.LayoutParams retrievePopupLayoutParamsFromPrefs( - final PopupPlayerUi playerUi) { - final SharedPreferences prefs = playerUi.getPlayer().getPrefs(); - final Context context = playerUi.getPlayer().getContext(); - - final boolean popupRememberSizeAndPos = prefs.getBoolean( - context.getString(R.string.popup_remember_size_pos_key), true); - final float defaultSize = context.getResources().getDimension(R.dimen.popup_default_width); - final float popupWidth = popupRememberSizeAndPos - ? prefs.getFloat(context.getString(R.string.popup_saved_width_key), defaultSize) - : defaultSize; - final float popupHeight = getMinimumVideoHeight(popupWidth); - - final WindowManager.LayoutParams popupLayoutParams = new WindowManager.LayoutParams( - (int) popupWidth, (int) popupHeight, - popupLayoutParamType(), - IDLE_WINDOW_FLAGS, - PixelFormat.TRANSLUCENT); - popupLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; - popupLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - - final int centerX = (int) (playerUi.getScreenWidth() / 2f - popupWidth / 2f); - final int centerY = (int) (playerUi.getScreenHeight() / 2f - popupHeight / 2f); - popupLayoutParams.x = popupRememberSizeAndPos - ? prefs.getInt(context.getString(R.string.popup_saved_x_key), centerX) : centerX; - popupLayoutParams.y = popupRememberSizeAndPos - ? prefs.getInt(context.getString(R.string.popup_saved_y_key), centerY) : centerY; - - return popupLayoutParams; - } - - public static void savePopupPositionAndSizeToPrefs(final PopupPlayerUi playerUi) { - if (playerUi.getPopupLayoutParams() != null) { - final Context context = playerUi.getPlayer().getContext(); - playerUi.getPlayer().getPrefs().edit() - .putFloat(context.getString(R.string.popup_saved_width_key), - playerUi.getPopupLayoutParams().width) - .putInt(context.getString(R.string.popup_saved_x_key), - playerUi.getPopupLayoutParams().x) - .putInt(context.getString(R.string.popup_saved_y_key), - playerUi.getPopupLayoutParams().y) - .apply(); - } - } - public static float getMinimumVideoHeight(final float width) { return width / (16.0f / 9.0f); // Respect the 16:9 ratio that most videos have } - @SuppressLint("RtlHardcoded") - public static WindowManager.LayoutParams buildCloseOverlayLayoutParams() { - final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - - final WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, - popupLayoutParamType(), - flags, - PixelFormat.TRANSLUCENT); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // Setting maximum opacity allowed for touch events to other apps for Android 12 and - // higher to prevent non interaction when using other apps with the popup player - closeOverlayLayoutParams.alpha = MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER; - } - - closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; - closeOverlayLayoutParams.softInputMode = - WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - return closeOverlayLayoutParams; - } - - public static int popupLayoutParamType() { - return Build.VERSION.SDK_INT < Build.VERSION_CODES.O - ? WindowManager.LayoutParams.TYPE_PHONE - : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - } - public static int retrieveSeekDurationFromPreferences(final Player player) { return Integer.parseInt(Objects.requireNonNull(player.getPrefs().getString( player.getContext().getString(R.string.seek_duration_key), 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 8283437f8..46396a840 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 @@ -2,21 +2,25 @@ package org.schabi.newpipe.player.ui; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static org.schabi.newpipe.MainActivity.DEBUG; -import static org.schabi.newpipe.player.helper.PlayerHelper.buildCloseOverlayLayoutParams; import static org.schabi.newpipe.player.helper.PlayerHelper.getMinimumVideoHeight; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePopupLayoutParamsFromPrefs; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.SuppressLint; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.PixelFormat; +import android.os.Build; import android.util.DisplayMetrics; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AnticipateInterpolator; import android.widget.LinearLayout; @@ -38,6 +42,20 @@ import org.schabi.newpipe.player.helper.PlayerHelper; public final class PopupPlayerUi extends VideoPlayerUi { private static final String TAG = PopupPlayerUi.class.getSimpleName(); + /** + * Maximum opacity allowed for Android 12 and higher to allow touches on other apps when using + * NewPipe's popup player. + * + *

+ * This value is hardcoded instead of being get dynamically with the method linked of the + * constant documentation below, because it is not static and popup player layout parameters + * are generated with static methods. + *

+ * + * @see WindowManager.LayoutParams#FLAG_NOT_TOUCHABLE + */ + private static final float MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER = 0.8f; + /*////////////////////////////////////////////////////////////////////////// // Popup player //////////////////////////////////////////////////////////////////////////*/ @@ -98,7 +116,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { updateScreenSize(); - popupLayoutParams = retrievePopupLayoutParamsFromPrefs(this); + popupLayoutParams = retrievePopupLayoutParamsFromPrefs(); binding.surfaceView.setHeights(popupLayoutParams.height, popupLayoutParams.height); checkPopupPositionBounds(); @@ -446,6 +464,92 @@ public final class PopupPlayerUi extends VideoPlayerUi { //endregion + /*////////////////////////////////////////////////////////////////////////// + // Popup & closing overlay layout params + saving popup position and size + //////////////////////////////////////////////////////////////////////////*/ + //region Popup & closing overlay layout params + saving popup position and size + + /** + * {@code screenWidth} and {@code screenHeight} must have been initialized. + * @return the popup starting layout params + */ + @SuppressLint("RtlHardcoded") + public WindowManager.LayoutParams retrievePopupLayoutParamsFromPrefs() { + final SharedPreferences prefs = getPlayer().getPrefs(); + final Context context = getPlayer().getContext(); + + final boolean popupRememberSizeAndPos = prefs.getBoolean( + context.getString(R.string.popup_remember_size_pos_key), true); + final float defaultSize = context.getResources().getDimension(R.dimen.popup_default_width); + final float popupWidth = popupRememberSizeAndPos + ? prefs.getFloat(context.getString(R.string.popup_saved_width_key), defaultSize) + : defaultSize; + final float popupHeight = getMinimumVideoHeight(popupWidth); + + final WindowManager.LayoutParams params = new WindowManager.LayoutParams( + (int) popupWidth, (int) popupHeight, + popupLayoutParamType(), + IDLE_WINDOW_FLAGS, + PixelFormat.TRANSLUCENT); + params.gravity = Gravity.LEFT | Gravity.TOP; + params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + + final int centerX = (int) (screenWidth / 2f - popupWidth / 2f); + final int centerY = (int) (screenHeight / 2f - popupHeight / 2f); + params.x = popupRememberSizeAndPos + ? prefs.getInt(context.getString(R.string.popup_saved_x_key), centerX) : centerX; + params.y = popupRememberSizeAndPos + ? prefs.getInt(context.getString(R.string.popup_saved_y_key), centerY) : centerY; + + return params; + } + + public void savePopupPositionAndSizeToPrefs() { + if (getPopupLayoutParams() != null) { + final Context context = getPlayer().getContext(); + getPlayer().getPrefs().edit() + .putFloat(context.getString(R.string.popup_saved_width_key), + popupLayoutParams.width) + .putInt(context.getString(R.string.popup_saved_x_key), + popupLayoutParams.x) + .putInt(context.getString(R.string.popup_saved_y_key), + popupLayoutParams.y) + .apply(); + } + } + + @SuppressLint("RtlHardcoded") + public static WindowManager.LayoutParams buildCloseOverlayLayoutParams() { + final int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + + final WindowManager.LayoutParams closeOverlayLayoutParams = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, + popupLayoutParamType(), + flags, + PixelFormat.TRANSLUCENT); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + // Setting maximum opacity allowed for touch events to other apps for Android 12 and + // higher to prevent non interaction when using other apps with the popup player + closeOverlayLayoutParams.alpha = MAXIMUM_OPACITY_ALLOWED_FOR_S_AND_HIGHER; + } + + closeOverlayLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; + closeOverlayLayoutParams.softInputMode = + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + return closeOverlayLayoutParams; + } + + public static int popupLayoutParamType() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.O + ? WindowManager.LayoutParams.TYPE_PHONE + : WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + } + //endregion + + /*////////////////////////////////////////////////////////////////////////// // Getters //////////////////////////////////////////////////////////////////////////*/ From 61c1da144e4b0a824d4816d9ecad6a59b53d9cf3 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Jul 2022 17:17:30 +0200 Subject: [PATCH 200/992] Some refactorings after review comments --- .../fragments/detail/VideoDetailFragment.java | 11 +++-- .../newpipe/local/dialog/PlaylistDialog.java | 2 +- .../schabi/newpipe/player/PlayerService.java | 23 ++++----- .../newpipe/player/ui/MainPlayerUi.java | 47 ++++++++----------- .../newpipe/player/ui/VideoPlayerUi.java | 19 +++----- 5 files changed, 42 insertions(+), 60 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 8ffff2f9e..5dc6bb436 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 @@ -1311,11 +1311,12 @@ public final class VideoDetailFragment setHeightThumbnail(); // Prevent from re-adding a view multiple times - new Handler().post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { - playerUi.removeViewFromParent(); - binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); - playerUi.setupVideoSurfaceIfNeeded(); - })); + new Handler(Looper.getMainLooper()).post(() -> + player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + })); } private void removeVideoPlayerView() { diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java index dec8b05b2..612c38181 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistDialog.java @@ -168,7 +168,7 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave final List streamEntities = Stream.of(player.getPlayQueue()) .filter(Objects::nonNull) - .flatMap(playQueue -> Objects.requireNonNull(playQueue).getStreams().stream()) + .flatMap(playQueue -> playQueue.getStreams().stream()) .map(StreamEntity::new) .collect(Collectors.toList()); if (streamEntities.isEmpty()) { 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 326b01590..14e8262d6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -33,8 +33,6 @@ import org.schabi.newpipe.util.ThemeHelper; /** * One service for all players. - * - * @author mauriciocolli */ public final class PlayerService extends Service { private static final String TAG = PlayerService.class.getSimpleName(); @@ -72,14 +70,16 @@ public final class PlayerService extends Service { + "], flags = [" + flags + "], startId = [" + startId + "]"); } - if (!Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) - || player.getPlayQueue() != null) { - // ^ no need to process media button's action if player is not working + if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction()) + && player.getPlayQueue() == null) { + // No need to process media button's actions if the player is not working, otherwise the + // player service would strangely start with nothing to play + return START_NOT_STICKY; + } - player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); - } + player.handleIntent(intent); + if (player.getMediaSessionManager() != null) { + player.getMediaSessionManager().handleMediaButtonIntent(intent); } return START_NOT_STICKY; @@ -97,11 +97,6 @@ public final class PlayerService extends Service { // We can't just pause the player here because it will make transition // from one stream to a new stream not smooth player.smoothStopForImmediateReusing(); - - // Notification shows information about old stream but if a user selects - // a stream from backStack it's not actual anymore - // So we should hide the notification at all. - // When autoplay enabled such notification flashing is annoying so skip this case } } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index d9f5ea7f4..278e4f1ff 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -75,6 +75,11 @@ import java.util.Optional; public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutChangeListener { private static final String TAG = MainPlayerUi.class.getSimpleName(); + // see the Javadoc of calculateMaxEndScreenThumbnailHeight for information + private static final int DETAIL_ROOT_MINIMUM_HEIGHT = 85; // dp + private static final int DETAIL_TITLE_TEXT_SIZE_TV = 16; // sp + private static final int DETAIL_TITLE_TEXT_SIZE_TABLET = 15; // sp + private boolean isFullscreen = false; private boolean isVerticalVideo = false; private boolean fragmentIsVisible = false; @@ -262,13 +267,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.topControls.setClickable(true); binding.topControls.setFocusable(true); - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - } + binding.titleTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); + binding.channelTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); } @Override @@ -450,13 +450,12 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh * The calculating follows these rules: *
    *
  • - * Show at least stream title and content creator on TVs and tablets - * when in landscape (always the case for TVs) and not in fullscreen mode. - * This requires to have at least 85dp free space for {@link R.id.detail_root} - * and additional space for the stream title text size - * ({@link R.id.detail_title_root_layout}). - * The text size is 15sp on tablets and 16sp on TVs, - * see {@link R.id.titleTextView}. + * Show at least stream title and content creator on TVs and tablets when in landscape + * (always the case for TVs) and not in fullscreen mode. This requires to have at least + * {@link #DETAIL_ROOT_MINIMUM_HEIGHT} free space for {@link R.id.detail_root} and + * additional space for the stream title text size ({@link R.id.detail_title_root_layout}). + * The text size is {@link #DETAIL_TITLE_TEXT_SIZE_TABLET} on tablets and + * {@link #DETAIL_TITLE_TEXT_SIZE_TV} on TVs, see {@link R.id.titleTextView}. *
  • *
  • * Otherwise, the max thumbnail height is the screen height. @@ -472,12 +471,12 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh final int screenHeight = context.getResources().getDisplayMetrics().heightPixels; if (DeviceUtils.isTv(context) && !isFullscreen()) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(16, context); + final int videoInfoHeight = DeviceUtils.dpToPx(DETAIL_ROOT_MINIMUM_HEIGHT, context) + + DeviceUtils.spToPx(DETAIL_TITLE_TEXT_SIZE_TV, context); return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); } else if (DeviceUtils.isTablet(context) && isLandscape() && !isFullscreen()) { - final int videoInfoHeight = - DeviceUtils.dpToPx(85, context) + DeviceUtils.spToPx(15, context); + final int videoInfoHeight = DeviceUtils.dpToPx(DETAIL_ROOT_MINIMUM_HEIGHT, context) + + DeviceUtils.spToPx(DETAIL_TITLE_TEXT_SIZE_TABLET, context); return Math.min(bitmap.getHeight(), screenHeight - videoInfoHeight); } else { // fullscreen player: max height is the device height return Math.min(bitmap.getHeight(), screenHeight); @@ -933,15 +932,9 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } fragmentListener.onFullscreenStateChanged(isFullscreen); - if (isFullscreen) { - binding.titleTextView.setVisibility(View.VISIBLE); - binding.channelTextView.setVisibility(View.VISIBLE); - binding.playerCloseButton.setVisibility(View.GONE); - } else { - binding.titleTextView.setVisibility(View.GONE); - binding.channelTextView.setVisibility(View.GONE); - binding.playerCloseButton.setVisibility(View.VISIBLE); - } + binding.titleTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); + binding.channelTextView.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); + binding.playerCloseButton.setVisibility(isFullscreen ? View.GONE : View.VISIBLE); setupScreenRotationButton(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 5b0be6f64..4d1065112 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.player.ui; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.ktx.ViewUtils.animate; @@ -912,18 +911,12 @@ public abstract class VideoPlayerUi extends PlayerUi public void onRepeatModeChanged(@RepeatMode final int repeatMode) { super.onRepeatModeChanged(repeatMode); - switch (repeatMode) { - case REPEAT_MODE_OFF: - binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_off); - break; - case REPEAT_MODE_ONE: - binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_one); - break; - case REPEAT_MODE_ALL: - binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_all); - break; - default: - break; // unreachable + if (repeatMode == REPEAT_MODE_ALL) { + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_all); + } else if (repeatMode == REPEAT_MODE_ONE) { + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_one); + } else /* repeatMode == REPEAT_MODE_OFF */ { + binding.repeatButton.setImageResource(R.drawable.exo_controls_repeat_off); } } From c03eac1dc99e9e61b863b5984444407dc8ee13a2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Jul 2022 17:50:12 +0200 Subject: [PATCH 201/992] Some SonarLint refactors --- .../player/gesture/BasePlayerGestureListener.kt | 3 ++- .../newpipe/player/gesture/DoubleTapListener.kt | 6 +++--- .../org/schabi/newpipe/player/ui/MainPlayerUi.java | 11 +++++------ .../org/schabi/newpipe/player/ui/PopupPlayerUi.java | 3 +-- .../org/schabi/newpipe/player/ui/VideoPlayerUi.java | 3 ++- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt index b006e73aa..555c34f96 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/BasePlayerGestureListener.kt @@ -1,6 +1,7 @@ package org.schabi.newpipe.player.gesture import android.os.Handler +import android.os.Looper import android.util.Log import android.view.GestureDetector import android.view.MotionEvent @@ -130,7 +131,7 @@ abstract class BasePlayerGestureListener( } private var doubleTapDelay = DOUBLE_TAP_DELAY - private val doubleTapHandler: Handler = Handler() + private val doubleTapHandler: Handler = Handler(Looper.getMainLooper()) private val doubleTapRunnable = Runnable { if (DEBUG) Log.d(TAG, "doubleTapRunnable called") diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt index 1a0b141e6..fc026abd9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/DoubleTapListener.kt @@ -1,7 +1,7 @@ package org.schabi.newpipe.player.gesture interface DoubleTapListener { - fun onDoubleTapStarted(portion: DisplayPortion) {} - fun onDoubleTapProgressDown(portion: DisplayPortion) {} - fun onDoubleTapFinished() {} + fun onDoubleTapStarted(portion: DisplayPortion) + fun onDoubleTapProgressDown(portion: DisplayPortion) + fun onDoubleTapFinished() } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 278e4f1ff..52a486add 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.player.ui; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu; import static org.schabi.newpipe.ktx.ViewUtils.animate; @@ -21,6 +22,7 @@ import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Color; import android.os.Handler; +import android.os.Looper; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; @@ -156,7 +158,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh .ifPresent(fragmentManager -> PlaylistDialog.showForPlayQueue(player, fragmentManager))); - settingsContentObserver = new ContentObserver(new Handler()) { + settingsContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { @Override public void onChange(final boolean selfChange) { setupScreenRotationButton(); @@ -237,8 +239,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh private void initVideoPlayer() { // restore last resize mode setResizeMode(PlayerHelper.retrieveResizeModeFromPrefs(player)); - binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + binding.getRoot().setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } @Override @@ -253,8 +254,7 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.getRoot().findViewById(R.id.metadataView).setVisibility(View.VISIBLE); binding.moreOptionsButton.setVisibility(View.VISIBLE); binding.topControls.setOrientation(LinearLayout.VERTICAL); - binding.primaryControls.getLayoutParams().width - = LinearLayout.LayoutParams.MATCH_PARENT; + binding.primaryControls.getLayoutParams().width = MATCH_PARENT; binding.secondaryControls.setVisibility(View.INVISIBLE); binding.moreOptionsButton.setImageDrawable(AppCompatResources.getDrawable(context, R.drawable.ic_expand_more)); @@ -459,7 +459,6 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh *
  • *
  • * Otherwise, the max thumbnail height is the screen height. - * TODO investigate why this is done on popup player, too *
  • *
* 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 46396a840..bb810f86b 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 @@ -287,8 +287,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { } final float minimumWidth = context.getResources().getDimension(R.dimen.popup_minimum_width); - final int actualWidth = (int) (width > screenWidth ? screenWidth - : (width < minimumWidth ? minimumWidth : width)); + final int actualWidth = Math.min((int) Math.max(width, minimumWidth), screenWidth); final int actualHeight = (int) getMinimumVideoHeight(width); if (DEBUG) { Log.d(TAG, "updatePopupSize() updated values:" diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 4d1065112..bdb327df1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -25,6 +25,7 @@ import android.graphics.PorterDuffColorFilter; import android.net.Uri; import android.os.Build; import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; @@ -103,7 +104,7 @@ public abstract class VideoPlayerUi extends PlayerUi //////////////////////////////////////////////////////////////////////////*/ protected PlayerBinding binding; - private final Handler controlsVisibilityHandler = new Handler(); + private final Handler controlsVisibilityHandler = new Handler(Looper.getMainLooper()); @Nullable private SurfaceHolderCallback surfaceHolderCallback; boolean surfaceIsSetup = false; @Nullable private Bitmap thumbnail = null; From 4443c908cb203e97df5e2624c34681aea2cfe129 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 9 Jul 2022 17:58:03 +0200 Subject: [PATCH 202/992] Fix SonarLint java:S5320, restrict broadcasts to app package --- .../java/org/schabi/newpipe/player/ui/VideoPlayerUi.java | 5 ++++- .../settings/custom/NotificationActionsPreference.java | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index bdb327df1..d38c8cfe4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -57,6 +57,7 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.CaptionStyleCompat; import com.google.android.exoplayer2.video.VideoSize; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.PlayerBinding; import org.schabi.newpipe.extractor.MediaFormat; @@ -1372,7 +1373,9 @@ public abstract class VideoPlayerUi extends PlayerUi } else if (v.getId() == binding.switchMute.getId()) { player.toggleMute(); } else if (v.getId() == binding.playerCloseButton.getId()) { - context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER)); + // set package to this app's package to prevent the intent from being seen outside + context.sendBroadcast(new Intent(VideoDetailFragment.ACTION_HIDE_MAIN_PLAYER) + .setPackage(App.PACKAGE_NAME)); } else if (v.getId() == binding.playbackSpeed.getId()) { onPlaybackSpeedClicked(); } else if (v.getId() == binding.qualityTextView.getId()) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index b4f6d598a..03b5a5a95 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -25,6 +25,7 @@ import androidx.core.graphics.drawable.DrawableCompat; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; +import org.schabi.newpipe.App; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.ListRadioIconItemBinding; import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding; @@ -62,7 +63,9 @@ public class NotificationActionsPreference extends Preference { public void onDetached() { super.onDetached(); saveChanges(); - getContext().sendBroadcast(new Intent(ACTION_RECREATE_NOTIFICATION)); + // set package to this app's package to prevent the intent from being seen outside + getContext().sendBroadcast(new Intent(ACTION_RECREATE_NOTIFICATION) + .setPackage(App.PACKAGE_NAME)); } From 8187a3bc04704e8b1cc380ccbf0c0274a4514494 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 10 Jul 2022 23:05:37 +0200 Subject: [PATCH 203/992] Move PlayerType into its own class and add documentation Also replace some `isPlayerOpen` with direct `playerType == null` checks. --- .../org/schabi/newpipe/RouterActivity.java | 6 ++-- .../fragments/detail/VideoDetailFragment.java | 2 +- .../list/channel/ChannelFragment.java | 2 +- .../list/playlist/PlaylistFragment.java | 2 +- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../org/schabi/newpipe/player/Player.java | 4 +-- .../schabi/newpipe/player/PlayerService.java | 5 --- .../org/schabi/newpipe/player/PlayerType.java | 32 +++++++++++++++++++ .../newpipe/player/helper/PlayerHelper.java | 9 ------ .../newpipe/player/helper/PlayerHolder.java | 7 ++-- .../schabi/newpipe/util/NavigationHelper.java | 28 ++++++++-------- 11 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/PlayerType.java diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 1194b4068..d055da1e8 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -60,7 +60,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.dialog.PlaylistDialog; -import org.schabi.newpipe.player.PlayerService; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; @@ -630,8 +630,8 @@ public class RouterActivity extends AppCompatActivity { } // ...the player is not running or in normal Video-mode/type - final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); - return playerType == null || playerType == PlayerService.PlayerType.MAIN; + final PlayerType playerType = PlayerHolder.getInstance().getType(); + return playerType == null || playerType == PlayerType.MAIN; } private void openAddToPlaylistDialog() { 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 5dc6bb436..92e7e4f16 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 @@ -90,7 +90,7 @@ import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.PlayerService; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.event.OnKeyDownListener; import org.schabi.newpipe.player.event.PlayerServiceExtendedEventListener; import org.schabi.newpipe.player.helper.PlayerHelper; 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 aabd64744..e44048473 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 @@ -43,7 +43,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.ktx.AnimationType; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.local.feed.notifications.NotificationHelper; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.util.ExtractorHelper; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 65fd8ada1..e3caeb522 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -43,7 +43,7 @@ import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.info_list.dialog.StreamDialogDefaultEntry; import org.schabi.newpipe.local.dialog.PlaylistDialog; import org.schabi.newpipe.local.playlist.RemotePlaylistManager; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.util.ExtractorHelper; 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 3bec07dcc..7fc72e064 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 @@ -43,7 +43,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.PlayerService.PlayerType; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.util.Localization; 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 2d44c6449..17b5a1985 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -32,7 +32,6 @@ import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.player.helper.PlayerHelper.isPlaybackResumeEnabled; import static org.schabi.newpipe.player.helper.PlayerHelper.nextRepeatMode; import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlaybackParametersFromPrefs; -import static org.schabi.newpipe.player.helper.PlayerHelper.retrievePlayerTypeFromIntent; import static org.schabi.newpipe.player.helper.PlayerHelper.retrieveSeekDurationFromPreferences; import static org.schabi.newpipe.player.helper.PlayerHelper.savePlaybackParametersToPrefs; import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; @@ -95,7 +94,6 @@ import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; @@ -308,7 +306,7 @@ public final class Player implements PlaybackListener, Listener { } final PlayerType oldPlayerType = playerType; - playerType = retrievePlayerTypeFromIntent(intent); + playerType = PlayerType.retrieveFromIntent(intent); initUIsForCurrentPlayerType(); // We need to setup audioOnly before super(), see "sourceOf" isAudioOnly = audioPlayerSelected(); 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 14e8262d6..8d982617a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PlayerService.java +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerService.java @@ -42,11 +42,6 @@ public final class PlayerService extends Service { private final IBinder mBinder = new PlayerService.LocalBinder(); - public enum PlayerType { - MAIN, - AUDIO, - POPUP - } /*////////////////////////////////////////////////////////////////////////// // Service's LifeCycle diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayerType.java b/app/src/main/java/org/schabi/newpipe/player/PlayerType.java new file mode 100644 index 000000000..171a70395 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/PlayerType.java @@ -0,0 +1,32 @@ +package org.schabi.newpipe.player; + +import static org.schabi.newpipe.player.Player.PLAYER_TYPE; + +import android.content.Intent; + +public enum PlayerType { + MAIN, + AUDIO, + POPUP; + + /** + * @return an integer representing this {@link PlayerType}, to be used to save it in intents + * @see #retrieveFromIntent(Intent) Use retrieveFromIntent() to retrieve and convert player type + * integers from an intent + */ + public int valueForIntent() { + return ordinal(); + } + + /** + * @param intent the intent to retrieve a player type from + * @return the player type integer retrieved from the intent, converted back into a {@link + * PlayerType}, or {@link PlayerType#MAIN} if there is no player type extra in the + * intent + * @throws ArrayIndexOutOfBoundsException if the intent contains an invalid player type integer + * @see #valueForIntent() Use valueForIntent() to obtain valid player type integers + */ + public static PlayerType retrieveFromIntent(final Intent intent) { + return values()[intent.getIntExtra(PLAYER_TYPE, MAIN.valueForIntent())]; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index d1d29dd71..fb346f5ba 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.player.helper; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; -import static org.schabi.newpipe.player.Player.PLAYER_TYPE; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_ALWAYS; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_NEVER; import static org.schabi.newpipe.player.helper.PlayerHelper.AutoplayType.AUTOPLAY_TYPE_WIFI; @@ -14,7 +13,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.SuppressLint; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; import android.provider.Settings; import android.view.accessibility.CaptioningManager; @@ -44,7 +42,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.SubtitlesStream; import org.schabi.newpipe.extractor.utils.Utils; import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; @@ -428,12 +425,6 @@ public final class PlayerHelper { // Utils used by player //////////////////////////////////////////////////////////////////////////// - public static PlayerService.PlayerType retrievePlayerTypeFromIntent(final Intent intent) { - // If you want to open popup from the app just include Constants.POPUP_ONLY into an extra - return PlayerService.PlayerType.values()[ - intent.getIntExtra(PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal())]; - } - public static boolean isPlaybackResumeEnabled(final Player player) { return player.getPrefs().getBoolean( player.getContext().getString(R.string.enable_watch_history_key), true) 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 cb613f854..5eaecd48d 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 @@ -18,6 +18,7 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.PlayerService; import org.schabi.newpipe.player.Player; +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; @@ -46,13 +47,13 @@ public final class PlayerHolder { @Nullable private Player player; /** - * Returns the current {@link PlayerService.PlayerType} of the {@link PlayerService} service, - * otherwise `null` if no service running. + * Returns the current {@link PlayerType} of the {@link PlayerService} service, + * otherwise `null` if no service is running. * * @return Current PlayerType */ @Nullable - public PlayerService.PlayerType getType() { + public PlayerType getType() { if (player == null) { return null; } 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 36b2bd46d..3b2c52691 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -51,9 +51,9 @@ import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; import org.schabi.newpipe.local.subscription.SubscriptionsImportFragment; import org.schabi.newpipe.player.PlayerService; -import org.schabi.newpipe.player.PlayerService.PlayerType; import org.schabi.newpipe.player.PlayQueueActivity; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.PlayerType; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.helper.PlayerHolder; import org.schabi.newpipe.player.playqueue.PlayQueue; @@ -91,7 +91,7 @@ public final class NavigationHelper { intent.putExtra(Player.PLAY_QUEUE_KEY, cacheKey); } } - intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.MAIN.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerType.MAIN.valueForIntent()); intent.putExtra(Player.RESUME_PLAYBACK, resumePlayback); return intent; @@ -164,7 +164,7 @@ public final class NavigationHelper { Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.POPUP.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerType.POPUP.valueForIntent()); ContextCompat.startForegroundService(context, intent); } @@ -175,7 +175,7 @@ public final class NavigationHelper { .show(); final Intent intent = getPlayerIntent(context, PlayerService.class, queue, resumePlayback); - intent.putExtra(Player.PLAYER_TYPE, PlayerService.PlayerType.AUDIO.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, PlayerType.AUDIO.valueForIntent()); ContextCompat.startForegroundService(context, intent); } @@ -186,15 +186,15 @@ public final class NavigationHelper { Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show(); final Intent intent = getPlayerEnqueueIntent(context, PlayerService.class, queue); - intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent()); ContextCompat.startForegroundService(context, intent); } public static void enqueueOnPlayer(final Context context, final PlayQueue queue) { PlayerType playerType = PlayerHolder.getInstance().getType(); - if (!PlayerHolder.getInstance().isPlayerOpen()) { + if (playerType == null) { Log.e(TAG, "Enqueueing but no player is open; defaulting to background player"); - playerType = PlayerService.PlayerType.AUDIO; + playerType = PlayerType.AUDIO; } enqueueOnPlayer(context, queue, playerType); @@ -203,14 +203,14 @@ public final class NavigationHelper { /* ENQUEUE NEXT */ public static void enqueueNextOnPlayer(final Context context, final PlayQueue queue) { PlayerType playerType = PlayerHolder.getInstance().getType(); - if (!PlayerHolder.getInstance().isPlayerOpen()) { + if (playerType == null) { Log.e(TAG, "Enqueueing next but no player is open; defaulting to background player"); - playerType = PlayerService.PlayerType.AUDIO; + playerType = PlayerType.AUDIO; } Toast.makeText(context, R.string.enqueued_next, Toast.LENGTH_SHORT).show(); final Intent intent = getPlayerEnqueueNextIntent(context, PlayerService.class, queue); - intent.putExtra(Player.PLAYER_TYPE, playerType.ordinal()); + intent.putExtra(Player.PLAYER_TYPE, playerType.valueForIntent()); ContextCompat.startForegroundService(context, intent); } @@ -414,14 +414,14 @@ public final class NavigationHelper { final boolean switchingPlayers) { final boolean autoPlay; - @Nullable final PlayerService.PlayerType playerType = PlayerHolder.getInstance().getType(); - if (!PlayerHolder.getInstance().isPlayerOpen()) { + @Nullable final PlayerType playerType = PlayerHolder.getInstance().getType(); + if (playerType == null) { // no player open autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else if (switchingPlayers) { // switching player to main player autoPlay = PlayerHolder.getInstance().isPlaying(); // keep play/pause state - } else if (playerType == PlayerService.PlayerType.MAIN) { + } else if (playerType == PlayerType.MAIN) { // opening new stream while already playing in main player autoPlay = PlayerHelper.isAutoplayAllowedByUser(context); } else { @@ -436,7 +436,7 @@ public final class NavigationHelper { // Situation when user switches from players to main player. All needed data is // here, we can start watching (assuming newQueue equals playQueue). // Starting directly in fullscreen if the previous player type was popup. - detailFragment.openVideoPlayer(playerType == PlayerService.PlayerType.POPUP + detailFragment.openVideoPlayer(playerType == PlayerType.POPUP || PlayerHelper.isStartMainPlayerFullscreenEnabled(context)); } else { detailFragment.selectAndLoadVideo(serviceId, url, title, playQueue); From 4536e8b55b59502471cd0b409f8b68e8cff600d2 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Jul 2022 01:48:52 -0400 Subject: [PATCH 204/992] Update some miscellaneous libraries --- app/build.gradle | 18 +++++++++--------- build.gradle | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9867037e6..787dd7833 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,7 +95,7 @@ android { } ext { - checkstyleVersion = '10.0' + checkstyleVersion = '10.3.1' androidxLifecycleVersion = '2.3.1' androidxRoomVersion = '2.4.2' @@ -110,7 +110,7 @@ ext { leakCanaryVersion = '2.5' stethoVersion = '1.6.0' mockitoVersion = '4.0.0' - assertJVersion = '3.22.0' + assertJVersion = '3.23.1' } configurations { @@ -179,7 +179,7 @@ sonarqube { dependencies { /** Desugaring **/ - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.6' /** NewPipe libraries **/ // You can use a local version by uncommenting a few lines in settings.gradle @@ -191,7 +191,7 @@ dependencies { /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" - ktlint 'com.pinterest:ktlint:0.44.0' + ktlint 'com.pinterest:ktlint:0.45.2' /** Kotlin **/ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" @@ -199,14 +199,14 @@ dependencies { /** AndroidX **/ implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation 'androidx.core:core-ktx:1.6.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.fragment:fragment-ktx:1.3.6' implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' - implementation 'androidx.media:media:1.5.0' + implementation 'androidx.media:media:1.6.0' implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "androidx.room:room-runtime:${androidxRoomVersion}" @@ -226,7 +226,7 @@ dependencies { kapt "frankiesardo:icepick-processor:${icepickVersion}" // HTML parser - implementation "org.jsoup:jsoup:1.14.3" + implementation "org.jsoup:jsoup:1.15.2" // HTTP client //noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users @@ -274,7 +274,7 @@ dependencies { implementation "com.jakewharton.rxbinding4:rxbinding:4.0.0" // Date and time formatting - implementation "org.ocpsoft.prettytime:prettytime:5.0.2.Final" + implementation "org.ocpsoft.prettytime:prettytime:5.0.3.Final" /** Debugging **/ // Memory leak detection diff --git a/build.gradle b/build.gradle index bea444fab..322a47a6f 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' + classpath 'com.android.tools.build:gradle:7.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong From c1e78cf55bf6e4178ac3ef3b4f7bae52c7f992c0 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Thu, 14 Jul 2022 03:23:45 -0400 Subject: [PATCH 205/992] Update OkHttp to 4.x --- app/build.gradle | 3 +-- app/src/main/java/org/schabi/newpipe/DownloaderImpl.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9867037e6..b5e26e8fc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -229,8 +229,7 @@ dependencies { implementation "org.jsoup:jsoup:1.14.3" // HTTP client - //noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users - implementation "com.squareup.okhttp3:okhttp:3.12.13" + implementation "com.squareup.okhttp3:okhttp:4.10.0" // Media player implementation "com.google.android.exoplayer:exoplayer-core:${exoPlayerVersion}" diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index f2803dc2f..79d2ad7b7 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -135,7 +135,7 @@ public final class DownloaderImpl extends Downloader { RequestBody requestBody = null; if (dataToSend != null) { - requestBody = RequestBody.create(null, dataToSend); + requestBody = RequestBody.create(dataToSend); } final okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder() From cc7a8fb1a611299bab6b574b75b602b38a23b2d9 Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Tue, 21 Jun 2022 20:22:39 +0300 Subject: [PATCH 206/992] Improve image placeholders - Show placeholders until the image is loaded because timeout can be very long and missing profile pictures and video thumbnails make app inconvenient to use - Adapt profile picture and video thumbnail placeholders to light theme - Replace profile picture and video thumbnail placeholders with vector graphics --- .../fragments/detail/VideoDetailFragment.java | 4 +- .../schabi/newpipe/util/PicassoHelper.java | 17 +++- app/src/main/res/drawable-nodpi/buddy.png | Bin 1168 -> 0 bytes .../res/drawable-nodpi/buddy_channel_item.png | Bin 1051 -> 0 bytes .../res/drawable-nodpi/dummy_thumbnail.png | Bin 956 -> 0 bytes .../drawable-nodpi/dummy_thumbnail_dark.png | Bin 105 -> 134 bytes .../dummy_thumbnail_playlist.png | Bin 1225 -> 0 bytes app/src/main/res/drawable/buddy.xml | 31 ++++++++ app/src/main/res/drawable/dummy_thumbnail.xml | 22 ++++++ .../res/drawable/dummy_thumbnail_playlist.xml | 30 +++++++ .../res/layout/list_channel_grid_item.xml | 2 +- .../res/layout/list_channel_mini_item.xml | 2 +- .../res/layout/list_comments_mini_item.xml | 2 +- .../res/layout/picker_subscription_item.xml | 2 +- app/src/main/res/values-night/colors.xml | 3 + app/src/main/res/values/colors.xml | 3 + assets/buddy_channel_item.svg | 73 ------------------ assets/dummy_thumbnail.svg | 7 ++ assets/dummy_thumbnail_playlist.svg | 8 ++ 19 files changed, 125 insertions(+), 81 deletions(-) delete mode 100644 app/src/main/res/drawable-nodpi/buddy.png delete mode 100644 app/src/main/res/drawable-nodpi/buddy_channel_item.png delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail.png delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_playlist.png create mode 100644 app/src/main/res/drawable/buddy.xml create mode 100644 app/src/main/res/drawable/dummy_thumbnail.xml create mode 100644 app/src/main/res/drawable/dummy_thumbnail_playlist.xml delete mode 100644 assets/buddy_channel_item.svg create mode 100644 assets/dummy_thumbnail.svg create mode 100644 assets/dummy_thumbnail_playlist.svg 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 278d472d4..78f3772e9 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 @@ -714,7 +714,7 @@ public final class VideoDetailFragment } private void initThumbnailViews(@NonNull final StreamInfo info) { - PicassoHelper.loadThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) + PicassoHelper.loadDetailsThumbnail(info.getThumbnailUrl()).tag(PICASSO_VIDEO_DETAILS_TAG) .into(binding.detailThumbnailImageView, new Callback() { @Override public void onSuccess() { @@ -2361,7 +2361,7 @@ public final class VideoDetailFragment binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle); binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader); binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark); - PicassoHelper.loadThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) + PicassoHelper.loadDetailsThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) .into(binding.overlayThumbnail); } diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index aabc459d0..223e8ac9c 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -104,6 +104,10 @@ public final class PicassoHelper { return loadImageDefault(url, R.drawable.dummy_thumbnail); } + public static RequestCreator loadDetailsThumbnail(final String url) { + return loadImageDefault(url, R.drawable.dummy_thumbnail, false); + } + public static RequestCreator loadBanner(final String url) { return loadImageDefault(url, R.drawable.channel_banner); } @@ -189,15 +193,24 @@ public final class PicassoHelper { private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { + return loadImageDefault(url, placeholderResId, true); + } + + private static RequestCreator loadImageDefault(final String url, final int placeholderResId, + final boolean showPlaceholderWhileLoading) { if (!shouldLoadImages || isBlank(url)) { return picassoInstance .load((String) null) .placeholder(placeholderResId) // show placeholder when no image should load .error(placeholderResId); } else { - return picassoInstance + final RequestCreator requestCreator = picassoInstance .load(url) - .error(placeholderResId); // don't show placeholder while loading, only on error + .error(placeholderResId); + if (showPlaceholderWhileLoading) { + requestCreator.placeholder(placeholderResId); + } + return requestCreator; } } } diff --git a/app/src/main/res/drawable-nodpi/buddy.png b/app/src/main/res/drawable-nodpi/buddy.png deleted file mode 100644 index 8713ee02bfa8cd492bf79ed4730ca4060ceeb631..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1168 zcmV;B1aJF^P)b~j1!&(iVkGA*yRLsUHxctK)V zbrwUKLgK~AqOlZmKgKJ|YYQQFIo?ozuk3;}eY~-(u?X@o$J-OLt0fRsC3shIYX#(- zNuCL1%5yBL~_YZH(P`V$pD zqy|Nz=7uyeXe13Uq?I5_ww#bl3bN7*A4KIKEK_Wd1xQxb3@jDYkVb%~h8CjwU@E7D)L^S7h2&r>r-a;rv3o+u0E`Xk zAPrb+$RGt+E2toy1Zz?eLH1y7PXrl(xiJmoD!`n$l0ZtZSCT*`U~fzTQ3=?K6#?V~ z12qJY9SpP~fJ|VZ2?3-B1NHPn6pYkqhqN%#Njs#1k@nglYZz&x9WsTHrrIF`7-^^- z(ua{U?T{2kO0`2Ij3l)~1dJ5>0ucdX86@`ppFO<%W-wA;J7j3tN0jiPaRVc*wL|tW z(m^}q2qU%LA3lWRpvN%`H1XxgbV~qfVW4BUR(0a6bI$P>d_(L(AznCe$_5LNcT*v+jXfvEir znCq2FKcw!%K>b=XJ&yxYb}t60>+zM-w&x0SWt3!Cq&I* zNwe-fkfsM~>K)$>DFtk5^F~NFgJn&-w?NJrtSh^`9@3_;u;ln!NFBq<;`()vQozzg z`5MShz}m$2<&X`mZuwHk3YNEi8DtCV+q?vFD`0)1d>(Ru3yJzU$T5Z!#m&=@OA1Fy zFHb?b49=9RCm;nJYW{yn1*fY12RX-Zs`&IDNDjx!)juH{IM?Q1kXFFCMEf^H4d7q{ z^%vw8PPYFO(j{=RwEF|Hgrlu~hg@SgT73NtGKaG*euZ2GoK0MRfvn(g>z|NL!r^51 z1G0nDl|LYTI9>J~a)jfx-ym~1-r_4nC2+jtE94%|*L;C2;C#z3kRF_`_Zf1A2VFiv zDtJ)!2~xm==AR%p@Sy%`|M z$uorG8CUd71_tH>o-U3d6}R4;Zxmo);9%JJhx3$K|NgTH#ezx%!PgEjI8j$!K5O|Z zzLvy2;(yc}-&{ENO`)%0bIaX(=gaM!R6~nC-9PwPV)Ci;kG~avnkTi?=a=2jTl4N{ zx83>Fc`SZLVO{lFf$g#n=dZlDu5RW8EBOcZD);-Ra}@48_}^gXWceW8kKC7nDm?av z{7}t$snUMO=#NqLMCqU`n}bt=oWDu=2UgEil(xxLHhi++!=AZ&CIl^H4fW#QnYATp z-6p3wuHM%=xgr;BUAkm~*Fw`!POgnF5?&l>dGbPKx=@gGNm7=gqISuY(;O>>OQu)| zDq5Fp6BaabN>AogG;e*E$K+Hb5W&qcv*ExL`y-M927R_qIE2){P2ly&x#?`@_^8`9 zg-Iy*n*g`aG+Pzc&MCG>8e9&^Epl)=I^oA9p|5Gv|9_O;FYs6>^>zA1iRm9dwuc(C zHD=zanXpxM$-BblK7*9o({(RbU6}0qM(zE{jZVSpOW18czUVz*{WmegW?q`K#E&;8 zIozMM@LrYpw@^xI>NEM`BpI9A=8v3S9eR{`I96BZiOuzlqdMoBoK|%@RYxi+A9<^5 zwW<2Je9uCyzi!yp4gH%GgXh|nsmIJ$}Rs%;$ZVd<&qPs zt>P~&b376sblSD>L>`vk$|pWayFmy*8bpJ zDRAea_X(MUx-~5p%@2A$3j{SdK2%I$V>9Mg0=K=XbS{$IP4O z{n?Q9EWx0EHvgt=%|hblFXA#zo8%>#r^fe*&f;-Q{rKYTc1xbzcb$J8XBILuTkltv z5oR&^XcH35FX-~?gUqFvqkWdPs{=l)e{oXdf!djZXKw!r)ap6^?$Eit+B>((C@Ahr z%8CD%wr7ifRPy0|&a>oA>)UI$``IM7zB>BV)^x8?{0f`8h6Y=%9gnIuywI@r*jZu| znvoyGwTrDTQesQewD3gn&#@o>{EqW2>-&>r*LXZ_(Z|zYQa)$A*kiinPNr$lkNR(` zm;6sR%zC#cI!)?-n#K9myA;1)`|)ty({I&=+su4<5++@__3=@4bX@G?s;5ox STg?c}*bJVoelF{r5}E*aKG6LD diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail.png b/app/src/main/res/drawable-nodpi/dummy_thumbnail.png deleted file mode 100644 index 86f454186e1f39cf9ca3ac28dd95502fdbce4655..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 956 zcmeAS@N?(olHy`uVBq!ia0y~yU|a=cf8YQT3?*&#{6LDa*vT`5;~7`oJ0;^v zbkO&@i;A40S_@}sggCW&t!Uwz8UW%7X@xkchOB7uS`olGH2}z62;_nkpmA}jU-kOz z?QT77ynJ~dP~_Fwoytz3TYq1XT)67y-s}+O@=*J%Z%?i;Mux_3 zttx%jux8czTeWX`0zzf=t(8Met1B)GE?gC5W4~Bx?de}%la}pR;k0Z=9mDFTWjn$` zFNj=Ub!z6LsL;URn2^Ojg@LhG){9#l-MR`adqqS%^n}QD{)96?Wl{-U-LH!OR=otO zRE(Y`e_)B`y3nto-Qsg60IAvE!BqKrE0CQfuM(X^i>I|_E!nISGXK@})Aa@JqL$g8 zm}h)>kQeHDc2>Ysn;nZ+MeTdl5Wos#o!Z^fmi4Rp@(R0a^3&@*dbEBXlnuRl__lyn z$kvL&a|?hT{wv|os`YerO&ch7mWLgHnG*aK9*E?oBd!%lXN&;yEws~T?4U2%!8 zGZ1W|y^5RZ-?x4t?_ckKX>j0(Z0M6x$B_N^avJup4{*IV!SU;B-T3be3%P7WzHRvT zT2`;>NC4{}%cpy^(mWcq62G*+x*om%eZ6_e{GzzjHa))zrV$y6Uoi3%n%#7VP>pe=l=;*57T*`9qKWzO`aqa+~mqO>CQ2 z)THPwm^~vpdBvu@-i2HhD|K63m`>kT3RDwn0E0NDPwL>%ybA~*haoIVPItuU|?H)Z&(<0e9y%;t!Ks7D}w%g6j_y9 zd%x&~zu$seOMqsVMa~vk_4YR(N9eKgi2-l3#XkMwT{Y!;egH2oIOXC?*T@N(n0)X> Ym3dv9k!XF~Gf=klboFyt=akR{0K-zPssI20 diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.png b/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.png index 02f698918fff753ac9d5bfe6091ac8349435243b..0e73416edeb6371b992c6c150b42d46a1c22f156 100644 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}K#X;^)4C~IxyaaMs(j9#r85lP9 zbN@+X1@buyJR*yMny-N{YhX3vTXZ8bmyq+$OArj%q2?+^5_!$^k V7#Ru_Qn`T=44$rjF6*2UngBC(9*6({ literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^?m(=;2qYLEakt!I&(;+6**`aRLM5QNh-iK2A&*Qo$V9E1zaSW-r_2yzD4^RoyhCjTU&MyAT<3;rZUGZhj37hA-&Z3Ve?Q;Lp*yG@)pezZZRsB|P*hnYQrnlAje`Hk3S|LIAbp7KR{)xjUW9eGv}CnXjKosifj&vP{) z{F(dzylTf|5>uUyZFrlof^F{pzEkUxCV%AMNDSI2zNq~zhh%cdvoxM%iL(y|@LKVM zj8;tU@ls6AYRP4AHcj8~jpa7OykiS~Yq zIX!PnLS(|NYz7z!bj^~ zZ=+f1Q{rrYJ0Hu6Q=7bT>b}GJ{6=YkH`g|%JekfenO*pyt7mzUT|+Nz(U+W(+CO@x_M8ooTwdrYDaq@nHhE&a9MCF8Dyo_L9U1(RTviPS^Ur$ZX;p6|W zncGadSpV0}?OJ}ynuom&F)JPwd-F;Dy`G0IbQBJ(j>R>r@XP{JfC-?%^>7C`(3r<)-(T2l)gh!>>E3+i$JRmUbd(d z&UHSqL)-N>gOT2n`cUKPZ@Ls7e+b?3S0Q<$p6hKEBfb6QnRhuZ9yV*~X%@c-B)fw^ zl8=Knyfu5660>x{f0bKp6AJ4Da_vvtun-5DwxZ_gUcb8=?kYU4`lJSw`}nit+wpHN v9Drp2gTsXVUlpc1f%7!MjLuFbP0l+XkK#Ah!m diff --git a/app/src/main/res/drawable/buddy.xml b/app/src/main/res/drawable/buddy.xml new file mode 100644 index 000000000..b7d23c4b1 --- /dev/null +++ b/app/src/main/res/drawable/buddy.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml new file mode 100644 index 000000000..5114fda32 --- /dev/null +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -0,0 +1,22 @@ + + + + diff --git a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml new file mode 100644 index 000000000..683b814c9 --- /dev/null +++ b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/app/src/main/res/layout/list_channel_grid_item.xml b/app/src/main/res/layout/list_channel_grid_item.xml index d9084bbe9..9f92c35a7 100644 --- a/app/src/main/res/layout/list_channel_grid_item.xml +++ b/app/src/main/res/layout/list_channel_grid_item.xml @@ -18,7 +18,7 @@ android:layout_centerHorizontal="true" android:layout_margin="2dp" android:contentDescription="@string/detail_uploader_thumbnail_view_description" - android:src="@drawable/buddy_channel_item" + android:src="@drawable/buddy" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index b66e07a12..e03f7c2fa 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy_channel_item" + android:src="@drawable/buddy" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml index 6bd363311..ba984e0ff 100644 --- a/app/src/main/res/layout/list_comments_mini_item.xml +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy_channel_item" + android:src="@drawable/buddy" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/picker_subscription_item.xml b/app/src/main/res/layout/picker_subscription_item.xml index c858ccc4e..f6e5f3587 100644 --- a/app/src/main/res/layout/picker_subscription_item.xml +++ b/app/src/main/res/layout/picker_subscription_item.xml @@ -22,7 +22,7 @@ android:layout_width="48dp" android:layout_height="48dp" app:shapeAppearance="@style/CircularImageView" - tools:src="@drawable/buddy_channel_item" /> + tools:src="@drawable/buddy" /> @color/white @color/white + + #6C6C6C + #999999
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 281fd9ce7..bbcc5d4bb 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,6 +6,9 @@ #CD201F + #F6F6F6 + #6E6E6E + #EEEEEE #EEEEEE diff --git a/assets/buddy_channel_item.svg b/assets/buddy_channel_item.svg deleted file mode 100644 index 4dec41f9d..000000000 --- a/assets/buddy_channel_item.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/assets/dummy_thumbnail.svg b/assets/dummy_thumbnail.svg new file mode 100644 index 000000000..bdea80b55 --- /dev/null +++ b/assets/dummy_thumbnail.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/dummy_thumbnail_playlist.svg b/assets/dummy_thumbnail_playlist.svg new file mode 100644 index 000000000..bd4b190aa --- /dev/null +++ b/assets/dummy_thumbnail_playlist.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 7b41acb781a994943318adb513fd9ccc9bc0b9f0 Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Thu, 7 Jul 2022 22:56:40 +0300 Subject: [PATCH 207/992] Use corresponding material icon in user profile thumbnail --- app/src/main/res/drawable/buddy.xml | 43 +++++++++++------------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/app/src/main/res/drawable/buddy.xml b/app/src/main/res/drawable/buddy.xml index b7d23c4b1..b1d8be868 100644 --- a/app/src/main/res/drawable/buddy.xml +++ b/app/src/main/res/drawable/buddy.xml @@ -1,31 +1,20 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> - - + android:pathData="M0,0 L24,0 L24,24 L0,24 z" + android:fillColor="@color/placeholder_foreground" /> + + + + + From 429f2536af73c1ac94626e5e32425e9036135bcf Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Thu, 7 Jul 2022 23:07:09 +0300 Subject: [PATCH 208/992] Optimize thumbnail placeholder drawables --- app/src/main/res/drawable/dummy_thumbnail.xml | 26 +++++--------- .../res/drawable/dummy_thumbnail_playlist.xml | 35 ++++++------------- 2 files changed, 18 insertions(+), 43 deletions(-) diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml index 5114fda32..bfa2a59cc 100644 --- a/app/src/main/res/drawable/dummy_thumbnail.xml +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -1,22 +1,12 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M0,0h24v24h-24z" + android:fillColor="@color/placeholder_background" /> + android:pathData="M15,11.9023L12.2188,13.3828L9.0898,15L9.0273,12.1641L9,9L11.8477,10.3555ZM15,11.9023" + android:fillColor="@color/placeholder_foreground" /> diff --git a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml index 683b814c9..2e74c16e2 100644 --- a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml +++ b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml @@ -1,30 +1,15 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + android:pathData="M0,0h24v24h-24z" + android:fillColor="@color/placeholder_background" /> + android:pathData="M13.8008,11.9023L11.0156,13.3828L7.8906,15L7.8281,12.1641L7.8008,9L10.6484,10.3555ZM13.8008,11.9023" + android:fillColor="@color/placeholder_foreground" /> + android:pathData="M16.8008,11.9023L14.0156,13.3828L10.8906,15L10.8281,12.1641L10.8008,9L13.6484,10.3555ZM16.8008,11.9023" + android:fillColor="@color/placeholder_foreground" /> From 35eeccd45a374caf3de21e90fd874151cb7f5cfe Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Wed, 13 Jul 2022 23:13:17 +0300 Subject: [PATCH 209/992] Rename buddy.xml to dummy_person.xml --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 3 ++- app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java | 2 +- app/src/main/res/drawable/{buddy.xml => dummy_person.xml} | 0 app/src/main/res/layout-large-land/fragment_video_detail.xml | 4 ++-- app/src/main/res/layout/channel_header.xml | 4 ++-- app/src/main/res/layout/fragment_video_detail.xml | 4 ++-- app/src/main/res/layout/list_channel_grid_item.xml | 2 +- app/src/main/res/layout/list_channel_item.xml | 2 +- app/src/main/res/layout/list_channel_mini_item.xml | 2 +- app/src/main/res/layout/list_comments_item.xml | 2 +- app/src/main/res/layout/list_comments_mini_item.xml | 2 +- app/src/main/res/layout/picker_subscription_item.xml | 2 +- app/src/main/res/layout/playlist_header.xml | 2 +- app/src/main/res/layout/select_channel_item.xml | 2 +- 14 files changed, 17 insertions(+), 16 deletions(-) rename app/src/main/res/drawable/{buddy.xml => dummy_person.xml} (100%) 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 78f3772e9..56e8f6d92 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 @@ -1551,7 +1551,8 @@ public final class VideoDetailFragment binding.detailUploaderThumbnailView.setVisibility(View.GONE); } - final Drawable buddyDrawable = AppCompatResources.getDrawable(activity, R.drawable.buddy); + final Drawable buddyDrawable + = AppCompatResources.getDrawable(activity, R.drawable.dummy_person); binding.detailSubChannelThumbnailView.setImageDrawable(buddyDrawable); binding.detailUploaderThumbnailView.setImageDrawable(buddyDrawable); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 223e8ac9c..04135702b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -97,7 +97,7 @@ public final class PicassoHelper { public static RequestCreator loadAvatar(final String url) { - return loadImageDefault(url, R.drawable.buddy); + return loadImageDefault(url, R.drawable.dummy_person); } public static RequestCreator loadThumbnail(final String url) { diff --git a/app/src/main/res/drawable/buddy.xml b/app/src/main/res/drawable/dummy_person.xml similarity index 100% rename from app/src/main/res/drawable/buddy.xml rename to app/src/main/res/drawable/dummy_person.xml diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index d8e52b354..1fecf93a5 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -271,7 +271,7 @@ android:layout_width="@dimen/video_item_detail_uploader_image_size" android:layout_height="@dimen/video_item_detail_uploader_image_size" android:contentDescription="@string/detail_uploader_thumbnail_view_description" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" /> diff --git a/app/src/main/res/layout/channel_header.xml b/app/src/main/res/layout/channel_header.xml index 3365e66e2..c2a70af02 100644 --- a/app/src/main/res/layout/channel_header.xml +++ b/app/src/main/res/layout/channel_header.xml @@ -33,7 +33,7 @@ android:layout_width="@dimen/channel_avatar_size" android:layout_height="@dimen/channel_avatar_size" android:padding="1dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" app:strokeWidth="2dp" /> @@ -44,7 +44,7 @@ android:layout_height="@dimen/sub_channel_avatar_size" android:layout_gravity="bottom|right" android:padding="1dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" android:visibility="gone" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 18655283e..49b6a807c 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -258,7 +258,7 @@ android:layout_width="@dimen/video_item_detail_uploader_image_size" android:layout_height="@dimen/video_item_detail_uploader_image_size" android:contentDescription="@string/detail_uploader_thumbnail_view_description" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" /> diff --git a/app/src/main/res/layout/list_channel_item.xml b/app/src/main/res/layout/list_channel_item.xml index c09c17ead..67bdac5f7 100644 --- a/app/src/main/res/layout/list_channel_item.xml +++ b/app/src/main/res/layout/list_channel_item.xml @@ -64,7 +64,7 @@ android:layout_height="@dimen/video_item_search_avatar_image_height" android:layout_marginLeft="@dimen/video_item_search_avatar_left_margin" android:layout_marginRight="@dimen/video_item_search_avatar_right_margin" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/itemTitleView" app:layout_constraintHorizontal_bias="0.5" diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index e03f7c2fa..747d22f52 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index 4358c438f..7cd7e066f 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -20,7 +20,7 @@ android:layout_marginLeft="3dp" android:layout_marginRight="@dimen/comment_item_avatar_right_margin" android:focusable="false" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml index ba984e0ff..e000b55c1 100644 --- a/app/src/main/res/layout/list_comments_mini_item.xml +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/picker_subscription_item.xml b/app/src/main/res/layout/picker_subscription_item.xml index f6e5f3587..237ed107a 100644 --- a/app/src/main/res/layout/picker_subscription_item.xml +++ b/app/src/main/res/layout/picker_subscription_item.xml @@ -22,7 +22,7 @@ android:layout_width="48dp" android:layout_height="48dp" app:shapeAppearance="@style/CircularImageView" - tools:src="@drawable/buddy" /> + tools:src="@drawable/dummy_person" /> diff --git a/app/src/main/res/layout/select_channel_item.xml b/app/src/main/res/layout/select_channel_item.xml index cfaaf2760..c77ff64fd 100644 --- a/app/src/main/res/layout/select_channel_item.xml +++ b/app/src/main/res/layout/select_channel_item.xml @@ -19,7 +19,7 @@ android:layout_alignParentTop="true" android:layout_marginStart="3dp" android:layout_marginRight="8dp" - android:src="@drawable/buddy" + android:src="@drawable/dummy_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> From dff1adb1adf7c8b82f657eab189ef49f447d23b2 Mon Sep 17 00:00:00 2001 From: krlvm <51774833+krlvm@users.noreply.github.com> Date: Wed, 13 Jul 2022 23:44:21 +0300 Subject: [PATCH 210/992] Fix swapped colors in video and playlist thumbnails --- .../main/res/drawable-night/dummy_person.xml | 20 ++++++++++++++++++ .../drawable-nodpi/dummy_thumbnail_dark.bmp | Bin 0 -> 62 bytes .../drawable-nodpi/dummy_thumbnail_dark.png | Bin 134 -> 0 bytes app/src/main/res/drawable/dummy_person.xml | 4 ++-- app/src/main/res/drawable/dummy_thumbnail.xml | 4 ++-- app/src/main/res/values/colors.xml | 4 ++-- 6 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 app/src/main/res/drawable-night/dummy_person.xml create mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.png diff --git a/app/src/main/res/drawable-night/dummy_person.xml b/app/src/main/res/drawable-night/dummy_person.xml new file mode 100644 index 000000000..b1d8be868 --- /dev/null +++ b/app/src/main/res/drawable-night/dummy_person.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp b/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp new file mode 100644 index 0000000000000000000000000000000000000000..f16e2a2bfe14d493e27d6c0686248d9a5223c1e0 GIT binary patch literal 62 jcmZ?rwPSz)DYhX3vTXZ8bmyq+$OArj%q2?+^5_!$^k V7#Ru_Qn`T=44$rjF6*2UngBC(9*6({ diff --git a/app/src/main/res/drawable/dummy_person.xml b/app/src/main/res/drawable/dummy_person.xml index b1d8be868..2b3229e8f 100644 --- a/app/src/main/res/drawable/dummy_person.xml +++ b/app/src/main/res/drawable/dummy_person.xml @@ -6,7 +6,7 @@ + android:fillColor="@color/placeholder_background" /> + android:fillColor="@color/placeholder_foreground" /> diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml index bfa2a59cc..6a04fc53e 100644 --- a/app/src/main/res/drawable/dummy_thumbnail.xml +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> + android:fillColor="@color/placeholder_foreground" /> + android:fillColor="@color/placeholder_background" /> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index bbcc5d4bb..25dd0fc86 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,8 +6,8 @@ #CD201F - #F6F6F6 - #6E6E6E + #6E6E6E + #F6F6F6 #EEEEEE From 7b9b9218dc72e9d29beb5bdd4cb08bca3545d36d Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 10:55:26 +0200 Subject: [PATCH 211/992] Remove bottom-sheet-thumbnail placeholder, clear the image instead --- .../fragments/detail/VideoDetailFragment.java | 2 +- .../res/drawable-nodpi/dummy_thumbnail_dark.bmp | Bin 62 -> 0 bytes app/src/main/res/layout/fragment_video_detail.xml | 3 +-- 3 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp 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 56e8f6d92..4e81101db 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 @@ -2361,7 +2361,7 @@ public final class VideoDetailFragment @Nullable final String thumbnailUrl) { binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle); binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader); - binding.overlayThumbnail.setImageResource(R.drawable.dummy_thumbnail_dark); + binding.overlayThumbnail.setImageDrawable(null); PicassoHelper.loadDetailsThumbnail(thumbnailUrl).tag(PICASSO_VIDEO_DETAILS_TAG) .into(binding.overlayThumbnail); } diff --git a/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp b/app/src/main/res/drawable-nodpi/dummy_thumbnail_dark.bmp deleted file mode 100644 index f16e2a2bfe14d493e27d6c0686248d9a5223c1e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62 jcmZ?rwPSz)D + android:scaleType="fitCenter" /> Date: Thu, 14 Jul 2022 10:56:28 +0200 Subject: [PATCH 212/992] Improve placeholder thumbnail SVGs and remove theme customization Theme customization does not seem to work well with Picasso: square/picasso#1275 --- .../main/res/drawable-night/dummy_person.xml | 20 ------------------- app/src/main/res/drawable/dummy_thumbnail.xml | 6 +++--- .../res/drawable/dummy_thumbnail_playlist.xml | 4 ++-- app/src/main/res/values-night/colors.xml | 3 --- app/src/main/res/values/colors.xml | 4 ++-- 5 files changed, 7 insertions(+), 30 deletions(-) delete mode 100644 app/src/main/res/drawable-night/dummy_person.xml diff --git a/app/src/main/res/drawable-night/dummy_person.xml b/app/src/main/res/drawable-night/dummy_person.xml deleted file mode 100644 index b1d8be868..000000000 --- a/app/src/main/res/drawable-night/dummy_person.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/dummy_thumbnail.xml b/app/src/main/res/drawable/dummy_thumbnail.xml index 6a04fc53e..aa81e03c9 100644 --- a/app/src/main/res/drawable/dummy_thumbnail.xml +++ b/app/src/main/res/drawable/dummy_thumbnail.xml @@ -5,8 +5,8 @@ android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml index 2e74c16e2..c56d558b8 100644 --- a/app/src/main/res/drawable/dummy_thumbnail_playlist.xml +++ b/app/src/main/res/drawable/dummy_thumbnail_playlist.xml @@ -7,9 +7,9 @@ android:pathData="M0,0h24v24h-24z" android:fillColor="@color/placeholder_background" /> diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index a0a0e891b..99026e70b 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -2,7 +2,4 @@ @color/white @color/white - - #6C6C6C - #999999 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 25dd0fc86..73e8a0cb1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -6,8 +6,8 @@ #CD201F - #6E6E6E - #F6F6F6 + #999999 + #6C6C6C #EEEEEE From 6ea85e6380f35a0123e8b64c847c0eb3b355c454 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 10:58:48 +0200 Subject: [PATCH 213/992] Rename dummy_* and more to placeholder_* --- .../fragments/detail/VideoDetailFragment.java | 2 +- .../newpipe/local/dialog/PlaylistAppendDialog.java | 3 ++- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../main/java/org/schabi/newpipe/player/Player.java | 2 +- .../java/org/schabi/newpipe/util/PicassoHelper.java | 10 +++++----- ...el_banner.png => placeholder_channel_banner.png} | Bin .../{dummy_person.xml => placeholder_person.xml} | 0 ...ylist.xml => placeholder_thumbnail_playlist.xml} | 0 ...humbnail.xml => placeholder_thumbnail_video.xml} | 0 .../res/layout-large-land/fragment_video_detail.xml | 8 ++++---- app/src/main/res/layout/channel_header.xml | 6 +++--- app/src/main/res/layout/fragment_video_detail.xml | 6 +++--- app/src/main/res/layout/item_stream_segment.xml | 2 +- app/src/main/res/layout/list_channel_grid_item.xml | 2 +- app/src/main/res/layout/list_channel_item.xml | 2 +- app/src/main/res/layout/list_channel_mini_item.xml | 2 +- app/src/main/res/layout/list_comments_item.xml | 2 +- app/src/main/res/layout/list_comments_mini_item.xml | 2 +- app/src/main/res/layout/list_playlist_grid_item.xml | 2 +- app/src/main/res/layout/list_playlist_item.xml | 2 +- app/src/main/res/layout/list_playlist_mini_item.xml | 2 +- app/src/main/res/layout/list_stream_grid_item.xml | 2 +- app/src/main/res/layout/list_stream_item.xml | 2 +- app/src/main/res/layout/list_stream_mini_item.xml | 2 +- .../res/layout/list_stream_playlist_grid_item.xml | 2 +- .../main/res/layout/list_stream_playlist_item.xml | 2 +- .../main/res/layout/picker_subscription_item.xml | 2 +- app/src/main/res/layout/play_queue_item.xml | 2 +- app/src/main/res/layout/player.xml | 2 +- app/src/main/res/layout/playlist_header.xml | 2 +- app/src/main/res/layout/select_channel_item.xml | 2 +- 31 files changed, 39 insertions(+), 38 deletions(-) rename app/src/main/res/drawable-nodpi/{channel_banner.png => placeholder_channel_banner.png} (100%) rename app/src/main/res/drawable/{dummy_person.xml => placeholder_person.xml} (100%) rename app/src/main/res/drawable/{dummy_thumbnail_playlist.xml => placeholder_thumbnail_playlist.xml} (100%) rename app/src/main/res/drawable/{dummy_thumbnail.xml => placeholder_thumbnail_video.xml} (100%) 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 4e81101db..c8ae0781e 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 @@ -1552,7 +1552,7 @@ public final class VideoDetailFragment } final Drawable buddyDrawable - = AppCompatResources.getDrawable(activity, R.drawable.dummy_person); + = AppCompatResources.getDrawable(activity, R.drawable.placeholder_person); binding.detailSubChannelThumbnailView.setImageDrawable(buddyDrawable); binding.detailUploaderThumbnailView.setImageDrawable(buddyDrawable); diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java index a874cdd62..a97eb0c18 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java @@ -145,7 +145,8 @@ public final class PlaylistAppendDialog extends PlaylistDialog { final Toast successToast = Toast.makeText(getContext(), R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); - if (playlist.thumbnailUrl.equals("drawable://" + R.drawable.dummy_thumbnail_playlist)) { + if (playlist.thumbnailUrl + .equals("drawable://" + R.drawable.placeholder_thumbnail_playlist)) { playlistDisposables.add(manager .changePlaylistThumbnail(playlist.uid, streams.get(0).getThumbnailUrl()) .observeOn(AndroidSchedulers.mainThread()) 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 4bffc0983..b4af73d08 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 @@ -613,7 +613,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment + tools:src="@drawable/placeholder_thumbnail_video" /> @@ -645,7 +645,7 @@ android:paddingLeft="@dimen/video_item_search_padding" android:paddingRight="@dimen/video_item_search_padding" android:scaleType="fitCenter" - android:src="@drawable/dummy_thumbnail" /> + android:src="@drawable/placeholder_thumbnail_video" /> @@ -44,7 +44,7 @@ android:layout_height="@dimen/sub_channel_avatar_size" android:layout_gravity="bottom|right" android:padding="1dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" android:visibility="gone" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 137dafee2..c794c5a55 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -47,7 +47,7 @@ android:scaleType="fitCenter" tools:ignore="RtlHardcoded" tools:layout_height="200dp" - tools:src="@drawable/dummy_thumbnail" /> + tools:src="@drawable/placeholder_thumbnail_video" /> diff --git a/app/src/main/res/layout/list_channel_item.xml b/app/src/main/res/layout/list_channel_item.xml index 67bdac5f7..cf4685e50 100644 --- a/app/src/main/res/layout/list_channel_item.xml +++ b/app/src/main/res/layout/list_channel_item.xml @@ -64,7 +64,7 @@ android:layout_height="@dimen/video_item_search_avatar_image_height" android:layout_marginLeft="@dimen/video_item_search_avatar_left_margin" android:layout_marginRight="@dimen/video_item_search_avatar_right_margin" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/itemTitleView" app:layout_constraintHorizontal_bias="0.5" diff --git a/app/src/main/res/layout/list_channel_mini_item.xml b/app/src/main/res/layout/list_channel_mini_item.xml index 747d22f52..473709bbd 100644 --- a/app/src/main/res/layout/list_channel_mini_item.xml +++ b/app/src/main/res/layout/list_channel_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index 7cd7e066f..ad73c5ff4 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -20,7 +20,7 @@ android:layout_marginLeft="3dp" android:layout_marginRight="@dimen/comment_item_avatar_right_margin" android:focusable="false" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml index e000b55c1..606a237c5 100644 --- a/app/src/main/res/layout/list_comments_mini_item.xml +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -17,7 +17,7 @@ android:layout_centerVertical="true" android:layout_marginStart="3dp" android:layout_marginRight="15dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_playlist_grid_item.xml b/app/src/main/res/layout/list_playlist_grid_item.xml index e052e9ac0..3ab69448d 100644 --- a/app/src/main/res/layout/list_playlist_grid_item.xml +++ b/app/src/main/res/layout/list_playlist_grid_item.xml @@ -17,7 +17,7 @@ android:layout_centerHorizontal="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:scaleType="fitStart" - android:src="@drawable/dummy_thumbnail_playlist" + android:src="@drawable/placeholder_thumbnail_playlist" tools:ignore="RtlHardcoded" /> diff --git a/app/src/main/res/layout/list_stream_item.xml b/app/src/main/res/layout/list_stream_item.xml index 1117a028e..793942568 100644 --- a/app/src/main/res/layout/list_stream_item.xml +++ b/app/src/main/res/layout/list_stream_item.xml @@ -15,7 +15,7 @@ android:layout_width="@dimen/video_item_search_thumbnail_image_width" android:layout_height="@dimen/video_item_search_thumbnail_image_height" android:scaleType="fitCenter" - android:src="@drawable/dummy_thumbnail" + android:src="@drawable/placeholder_thumbnail_video" app:layout_constraintBottom_toTopOf="@+id/itemProgressView" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/layout/list_stream_mini_item.xml b/app/src/main/res/layout/list_stream_mini_item.xml index d33c9d4ba..2eab66eaf 100644 --- a/app/src/main/res/layout/list_stream_mini_item.xml +++ b/app/src/main/res/layout/list_stream_mini_item.xml @@ -18,7 +18,7 @@ android:layout_alignParentTop="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:scaleType="fitCenter" - android:src="@drawable/dummy_thumbnail" + android:src="@drawable/placeholder_thumbnail_video" tools:ignore="RtlHardcoded" /> + tools:src="@drawable/placeholder_person" /> diff --git a/app/src/main/res/layout/player.xml b/app/src/main/res/layout/player.xml index b8b60b3f8..60cbcf7c4 100644 --- a/app/src/main/res/layout/player.xml +++ b/app/src/main/res/layout/player.xml @@ -397,7 +397,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="2dp" - android:src="@drawable/dummy_thumbnail" + android:src="@drawable/placeholder_thumbnail_video" android:visibility="gone" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/playlist_header.xml b/app/src/main/res/layout/playlist_header.xml index 39eca2fda..9c038db3a 100644 --- a/app/src/main/res/layout/playlist_header.xml +++ b/app/src/main/res/layout/playlist_header.xml @@ -45,7 +45,7 @@ android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:padding="0.7dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" app:strokeColor="#ffffff" app:strokeWidth="1dp" /> diff --git a/app/src/main/res/layout/select_channel_item.xml b/app/src/main/res/layout/select_channel_item.xml index c77ff64fd..c5fd51bb8 100644 --- a/app/src/main/res/layout/select_channel_item.xml +++ b/app/src/main/res/layout/select_channel_item.xml @@ -19,7 +19,7 @@ android:layout_alignParentTop="true" android:layout_marginStart="3dp" android:layout_marginRight="8dp" - android:src="@drawable/dummy_person" + android:src="@drawable/placeholder_person" app:shapeAppearance="@style/CircularImageView" tools:ignore="RtlHardcoded" /> From 9f993e0c49f2c0cf1f48205b584096621779eece Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 14:47:54 +0200 Subject: [PATCH 214/992] Make video and playlist placeholder thumbnails 16:9 After making the playlist and video thumbnails' scaleType fitCenter, the 24dp*24dp thumbnails would appear as a square, which would be strange, since the image view is 16:9. --- .../main/res/drawable/placeholder_thumbnail_playlist.xml | 8 ++++---- app/src/main/res/drawable/placeholder_thumbnail_video.xml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml b/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml index c56d558b8..de286d860 100644 --- a/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml +++ b/app/src/main/res/drawable/placeholder_thumbnail_playlist.xml @@ -1,15 +1,15 @@ diff --git a/app/src/main/res/drawable/placeholder_thumbnail_video.xml b/app/src/main/res/drawable/placeholder_thumbnail_video.xml index aa81e03c9..0b262f923 100644 --- a/app/src/main/res/drawable/placeholder_thumbnail_video.xml +++ b/app/src/main/res/drawable/placeholder_thumbnail_video.xml @@ -1,12 +1,12 @@ From 0e8cc72b134bf5755f59cbbfd6e01180509e7c57 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jul 2022 22:14:03 +0200 Subject: [PATCH 215/992] Fix random NullPointerException when adding video player view --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 92e7e4f16..0b32a5e29 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 @@ -1305,7 +1305,7 @@ public final class VideoDetailFragment } private void addVideoPlayerView() { - if (!isPlayerAvailable()) { + if (!isPlayerAvailable() || getView() == null) { return; } setHeightThumbnail(); From 25a43b57b20ec686b6a5a9c4eb44241a6b2ab864 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 10 Jul 2022 14:19:37 +0200 Subject: [PATCH 216/992] Updated checkstyle So that the assign operators are used on the same branch --- checkstyle/checkstyle.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/checkstyle/checkstyle.xml b/checkstyle/checkstyle.xml index 282358f6a..ce378a65f 100644 --- a/checkstyle/checkstyle.xml +++ b/checkstyle/checkstyle.xml @@ -116,6 +116,10 @@ + + + + From 8b209df253fe8e11377fed82b2e45ba87f4c4a02 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 10 Jul 2022 14:19:58 +0200 Subject: [PATCH 217/992] Changed the code accordingly + Removed some unused code --- .../material/appbar/FlingBehavior.java | 4 +- .../org/schabi/newpipe/DownloaderImpl.java | 10 +- .../java/org/schabi/newpipe/MainActivity.java | 8 +- .../newpipe/download/DownloadDialog.java | 12 +- .../schabi/newpipe/error/ErrorActivity.java | 8 +- .../fragments/detail/DescriptionFragment.java | 16 +- .../newpipe/info_list/InfoItemBuilder.java | 4 +- .../dialog/StreamDialogDefaultEntry.java | 4 +- .../holder/CommentsMiniInfoItemHolder.java | 9 +- .../holder/StreamMiniInfoItemHolder.java | 7 +- .../local/bookmark/BookmarkFragment.java | 4 +- .../local/dialog/PlaylistCreationDialog.java | 4 +- .../local/playlist/LocalPlaylistFragment.java | 10 +- .../services/SubscriptionsExportService.java | 8 +- .../newpipe/player/helper/CacheFactory.java | 4 +- .../player/helper/PlayerDataSource.java | 4 +- .../newpipe/player/helper/PlayerHelper.java | 4 +- .../player/playback/PlayerMediaSession.java | 9 +- .../settings/ContentSettingsFragment.java | 12 +- .../settings/DebugSettingsFragment.java | 30 +- .../settings/DownloadSettingsFragment.java | 8 +- .../PeertubeInstanceListFragment.java | 4 +- .../settings/UpdateSettingsFragment.java | 12 +- .../schabi/newpipe/streams/DataReader.java | 4 +- .../newpipe/streams/Mp4FromDashWriter.java | 4 +- .../org/schabi/newpipe/util/ListHelper.java | 29 +- .../schabi/newpipe/util/PeertubeHelper.java | 4 +- .../schabi/newpipe/util/PermissionHelper.java | 4 +- .../schabi/newpipe/util/PicassoHelper.java | 4 +- .../org/schabi/newpipe/util/StateSaver.java | 8 +- .../InternalUrlsHandler.java | 8 +- .../util/urlfinder/PatternsCompat.java | 274 +----------------- .../newpipe/views/FocusAwareDrawerLayout.java | 8 +- .../services/ImportExportJsonHelperTest.java | 10 +- .../settings/tabs/TabsJsonHelperTest.java | 14 +- 35 files changed, 153 insertions(+), 413 deletions(-) diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index 3e5f408f7..47c8deb83 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -132,8 +132,8 @@ public final class FlingBehavior extends AppBarLayout.Behavior { try { final Class headerBehaviorType = this.getClass().getSuperclass().getSuperclass(); if (headerBehaviorType != null) { - final Field field - = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef"); + final Field field = + headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef"); field.setAccessible(true); return field; } diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index f2803dc2f..3579a0bad 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -1,5 +1,7 @@ package org.schabi.newpipe; +import static org.schabi.newpipe.MainActivity.DEBUG; + import android.content.Context; import androidx.annotation.NonNull; @@ -26,10 +28,10 @@ import okhttp3.RequestBody; import okhttp3.ResponseBody; public final class DownloaderImpl extends Downloader { - public static final String USER_AGENT - = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; - public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY - = "youtube_restricted_mode_key"; + public static final String USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; + public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY = + "youtube_restricted_mode_key"; public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000"; public static final String YOUTUBE_DOMAIN = "youtube.com"; diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index dd59eeb25..4a982874c 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -473,8 +473,8 @@ public class MainActivity extends AppCompatActivity { ErrorUtil.showUiErrorSnackbar(this, "Setting up service toggle", e); } - final SharedPreferences sharedPreferences - = PreferenceManager.getDefaultSharedPreferences(this); + final SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(this); if (sharedPreferences.getBoolean(Constants.KEY_THEME_CHANGE, false)) { if (DEBUG) { Log.d(TAG, "Theme has changed, recreating activity..."); @@ -646,8 +646,8 @@ public class MainActivity extends AppCompatActivity { } super.onCreateOptionsMenu(menu); - final Fragment fragment - = getSupportFragmentManager().findFragmentById(R.id.fragment_holder); + final Fragment fragment = + getSupportFragmentManager().findFragmentById(R.id.fragment_holder); if (!(fragment instanceof SearchFragment)) { toolbarLayoutBinding.toolbarSearchContainer.getRoot().setVisibility(View.GONE); } diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index e4adddc2a..0e64e8b48 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.download; +import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP; +import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery; +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; + import android.app.Activity; import android.content.ComponentName; import android.content.Context; @@ -82,10 +86,6 @@ import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder; import us.shandian.giga.service.MissionState; -import static org.schabi.newpipe.extractor.stream.DeliveryMethod.PROGRESSIVE_HTTP; -import static org.schabi.newpipe.util.ListHelper.getStreamsOfSpecifiedDelivery; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { private static final String TAG = "DialogFragment"; @@ -205,8 +205,8 @@ public class DownloadDialog extends DialogFragment setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context)); Icepick.restoreInstanceState(this, savedInstanceState); - final SparseArray> secondaryStreams - = new SparseArray<>(4); + final SparseArray> secondaryStreams = + new SparseArray<>(4); final List videoStreams = wrappedVideoStreams.getStreamsList(); for (int i = 0; i < videoStreams.size(); i++) { diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index bd8430296..8b2ac37dc 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -65,11 +65,11 @@ public class ErrorActivity extends AppCompatActivity { public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org"; public static final String ERROR_EMAIL_SUBJECT = "Exception in "; - public static final String ERROR_GITHUB_ISSUE_URL - = "https://github.com/TeamNewPipe/NewPipe/issues"; + public static final String ERROR_GITHUB_ISSUE_URL = + "https://github.com/TeamNewPipe/NewPipe/issues"; - public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER - = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + public static final DateTimeFormatter CURRENT_TIMESTAMP_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); private ErrorInfo errorInfo; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index d57ddb02d..33fbe5db1 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.fragments.detail; +import static android.text.TextUtils.isEmpty; +import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; +import static org.schabi.newpipe.extractor.utils.Utils.isBlank; + import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -33,10 +37,6 @@ import java.util.List; import icepick.State; import io.reactivex.rxjava3.disposables.CompositeDisposable; -import static android.text.TextUtils.isEmpty; -import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; -import static org.schabi.newpipe.extractor.utils.Utils.isBlank; - public class DescriptionFragment extends BaseFragment { @State @@ -185,8 +185,8 @@ public class DescriptionFragment extends BaseFragment { return; } - final ItemMetadataBinding itemBinding - = ItemMetadataBinding.inflate(inflater, layout, false); + final ItemMetadataBinding itemBinding = + ItemMetadataBinding.inflate(inflater, layout, false); itemBinding.metadataTypeView.setText(type); itemBinding.metadataTypeView.setOnLongClickListener(v -> { @@ -206,8 +206,8 @@ public class DescriptionFragment extends BaseFragment { private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { if (streamInfo.getTags() != null && !streamInfo.getTags().isEmpty()) { - final ItemMetadataTagsBinding itemBinding - = ItemMetadataTagsBinding.inflate(inflater, layout, false); + final ItemMetadataTagsBinding itemBinding = + ItemMetadataTagsBinding.inflate(inflater, layout, false); final List tags = new ArrayList<>(streamInfo.getTags()); Collections.sort(tags); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index d78bf1076..68f19ee97 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -67,8 +67,8 @@ public class InfoItemBuilder { public View buildView(@NonNull final ViewGroup parent, @NonNull final InfoItem infoItem, final HistoryRecordManager historyRecordManager, final boolean useMiniVariant) { - final InfoItemHolder holder - = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); + final InfoItemHolder holder = + holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); holder.updateFromItem(infoItem, historyRecordManager); return holder.itemView; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java index d03546593..2b3f73926 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java @@ -115,8 +115,8 @@ public enum StreamDialogDefaultEntry { DOWNLOAD(R.string.download, (fragment, item) -> fetchStreamInfoAndSaveToDatabase(fragment.requireContext(), item.getServiceId(), item.getUrl(), info -> { - final DownloadDialog downloadDialog - = new DownloadDialog(fragment.requireContext(), info); + final DownloadDialog downloadDialog = + new DownloadDialog(fragment.requireContext(), info); downloadDialog.show(fragment.getChildFragmentManager(), "downloadDialog"); }) ), diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index 6e4773c09..b900750a8 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -23,9 +23,9 @@ import org.schabi.newpipe.util.CommentTextOnTouchListener; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.external_communication.TimestampExtractor; -import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.PicassoHelper; +import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.external_communication.TimestampExtractor; import java.util.regex.Matcher; @@ -204,8 +204,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { boolean hasEllipsis = false; if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) { - final int endOfLastLine - = itemContentView.getLayout().getLineEnd(COMMENT_DEFAULT_LINES - 1); + final int endOfLastLine = itemContentView + .getLayout() + .getLineEnd(COMMENT_DEFAULT_LINES - 1); int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine - 2); if (end == -1) { end = Math.max(endOfLastLine - 2, 0); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 54d31ca57..8d17017d2 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -14,8 +14,8 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.ktx.ViewUtils; import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.util.StreamTypeUtil; import org.schabi.newpipe.views.AnimatedProgressBar; @@ -111,8 +111,9 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { final HistoryRecordManager historyRecordManager) { final StreamInfoItem item = (StreamInfoItem) infoItem; - final StreamStateEntity state - = historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; + final StreamStateEntity state = historyRecordManager + .loadStreamState(infoItem) + .blockingGet()[0]; if (state != null && item.getDuration() > 0 && !StreamTypeUtil.isLiveStream(item.getStreamType())) { itemProgressView.setMax((int) item.getDuration()); diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index f272a8831..ac11d007f 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -256,8 +256,8 @@ public final class BookmarkFragment extends BaseLocalListFragment playlistIter = playlist.iterator(); // History data - final HistoryRecordManager recordManager - = new HistoryRecordManager(getContext()); + final HistoryRecordManager recordManager = + new HistoryRecordManager(getContext()); final Iterator historyIter = recordManager .getStreamHistorySortedById().blockingFirst().iterator(); @@ -544,8 +544,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment { - final List result - = new ArrayList<>(subscriptionEntities.size()); + final List result = + new ArrayList<>(subscriptionEntities.size()); for (final SubscriptionEntity entity : subscriptionEntities) { result.add(new SubscriptionItem(entity.getServiceId(), entity.getUrl(), entity.getName())); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java index d189616d1..41fcc823a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/CacheFactory.java @@ -39,8 +39,8 @@ final class CacheFactory implements DataSource.Factory { .createDataSource(); final FileDataSource fileSource = new FileDataSource(); - final CacheDataSink dataSink - = new CacheDataSink(cache, PlayerHelper.getPreferredFileSize()); + final CacheDataSink dataSink = + new CacheDataSink(cache, PlayerHelper.getPreferredFileSize()); return new CacheDataSource(cache, dataSource, fileSource, dataSink, CACHE_FLAGS, null); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java index 88f25e194..0530d56e9 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerDataSource.java @@ -208,8 +208,8 @@ public class PlayerDataSource { Log.w(TAG, "instantiateCacheIfNeeded: could not create cache dir"); } - final LeastRecentlyUsedCacheEvictor evictor - = new LeastRecentlyUsedCacheEvictor(PlayerHelper.getPreferredCacheSize()); + final LeastRecentlyUsedCacheEvictor evictor = + new LeastRecentlyUsedCacheEvictor(PlayerHelper.getPreferredCacheSize()); cache = new SimpleCache(cacheDir, evictor, new StandaloneDatabaseProvider(context)); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index fb346f5ba..abde7c3d1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -62,8 +62,8 @@ import java.util.concurrent.TimeUnit; public final class PlayerHelper { private static final StringBuilder STRING_BUILDER = new StringBuilder(); - private static final Formatter STRING_FORMATTER - = new Formatter(STRING_BUILDER, Locale.getDefault()); + private static final Formatter STRING_FORMATTER = + new Formatter(STRING_BUILDER, Locale.getDefault()); private static final NumberFormat SPEED_FORMATTER = new DecimalFormat("0.##x"); private static final NumberFormat PITCH_FORMATTER = new DecimalFormat("##%"); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java index 3be9b6173..3c41acc75 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java @@ -61,8 +61,7 @@ public class PlayerMediaSession implements MediaSessionCallback { return null; } - final MediaDescriptionCompat.Builder descriptionBuilder - = new MediaDescriptionCompat.Builder() + final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() .setMediaId(String.valueOf(index)) .setTitle(item.getTitle()) .setSubtitle(item.getUploader()); @@ -76,14 +75,14 @@ public class PlayerMediaSession implements MediaSessionCallback { additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); - descriptionBuilder.setExtras(additionalMetadata); + descBuilder.setExtras(additionalMetadata); final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); if (thumbnailUri != null) { - descriptionBuilder.setIconUri(thumbnailUri); + descBuilder.setIconUri(thumbnailUri); } - return descriptionBuilder.build(); + return descBuilder.build(); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 47458ad3f..37f83057b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -43,8 +43,8 @@ import java.util.Objects; public class ContentSettingsFragment extends BasePreferenceFragment { private static final String ZIP_MIME_TYPE = "application/zip"; - private final SimpleDateFormat exportDateFormat - = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); + private final SimpleDateFormat exportDateFormat = + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); private ContentSettingsManager manager; @@ -160,8 +160,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment { // will be saved only on success final Uri lastExportDataUri = result.getData().getData(); - final StoredFileHelper file - = new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); + final StoredFileHelper file = + new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); exportDatabase(file, lastExportDataUri); } @@ -173,8 +173,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment { // will be saved only on success final Uri lastImportDataUri = result.getData().getData(); - final StoredFileHelper file - = new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); + final StoredFileHelper file = + new StoredFileHelper(getContext(), result.getData().getData(), ZIP_MIME_TYPE); new AlertDialog.Builder(requireActivity()) .setMessage(R.string.override_current_data) diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java index dd9f5fb1f..0f4c9765e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java @@ -9,8 +9,8 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; -import org.schabi.newpipe.util.PicassoHelper; import org.schabi.newpipe.local.feed.notifications.NotificationWorker; +import org.schabi.newpipe.util.PicassoHelper; import java.util.Optional; @@ -21,20 +21,20 @@ public class DebugSettingsFragment extends BasePreferenceFragment { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { addPreferencesFromResourceRegistry(); - final Preference allowHeapDumpingPreference - = findPreference(getString(R.string.allow_heap_dumping_key)); - final Preference showMemoryLeaksPreference - = findPreference(getString(R.string.show_memory_leaks_key)); - final Preference showImageIndicatorsPreference - = findPreference(getString(R.string.show_image_indicators_key)); - final Preference checkNewStreamsPreference - = findPreference(getString(R.string.check_new_streams_key)); - final Preference crashTheAppPreference - = findPreference(getString(R.string.crash_the_app_key)); - final Preference showErrorSnackbarPreference - = findPreference(getString(R.string.show_error_snackbar_key)); - final Preference createErrorNotificationPreference - = findPreference(getString(R.string.create_error_notification_key)); + final Preference allowHeapDumpingPreference = + findPreference(getString(R.string.allow_heap_dumping_key)); + final Preference showMemoryLeaksPreference = + findPreference(getString(R.string.show_memory_leaks_key)); + final Preference showImageIndicatorsPreference = + findPreference(getString(R.string.show_image_indicators_key)); + final Preference checkNewStreamsPreference = + findPreference(getString(R.string.check_new_streams_key)); + final Preference crashTheAppPreference = + findPreference(getString(R.string.crash_the_app_key)); + final Preference showErrorSnackbarPreference = + findPreference(getString(R.string.show_error_snackbar_key)); + final Preference createErrorNotificationPreference = + findPreference(getString(R.string.create_error_notification_key)); assert allowHeapDumpingPreference != null; assert showMemoryLeaksPreference != null; diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index 74dc9f63e..5a4300cdd 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.settings; +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; + import android.app.Activity; import android.content.ContentResolver; import android.content.Context; @@ -32,8 +34,6 @@ import java.net.URI; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - public class DownloadSettingsFragment extends BasePreferenceFragment { public static final boolean IGNORE_RELEASE_ON_OLD_PATH = true; private String downloadPathVideoPreference; @@ -255,8 +255,8 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { context.grantUriPermission(context.getPackageName(), uri, StoredDirectoryHelper.PERMISSION_FLAGS); - final StoredDirectoryHelper mainStorage - = new StoredDirectoryHelper(context, uri, null); + final StoredDirectoryHelper mainStorage = + new StoredDirectoryHelper(context, uri, null); Log.i(TAG, "Acquiring tree success from " + uri.toString()); if (!mainStorage.canWrite()) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index 1ff7947fd..92b9a0362 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -199,8 +199,8 @@ public class PeertubeInstanceListFragment extends Fragment { } private void showAddItemDialog(final Context c) { - final DialogEditTextBinding dialogBinding - = DialogEditTextBinding.inflate(getLayoutInflater()); + final DialogEditTextBinding dialogBinding = + DialogEditTextBinding.inflate(getLayoutInflater()); dialogBinding.dialogEditText.setInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); dialogBinding.dialogEditText.setHint(R.string.peertube_instance_add_help); diff --git a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java index 1043e88c2..f1f63ffdf 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/UpdateSettingsFragment.java @@ -9,19 +9,19 @@ import org.schabi.newpipe.NewVersionWorker; import org.schabi.newpipe.R; public class UpdateSettingsFragment extends BasePreferenceFragment { - private final Preference.OnPreferenceChangeListener updatePreferenceChange - = (preference, checkForUpdates) -> { + private final Preference.OnPreferenceChangeListener updatePreferenceChange = (p, nVal) -> { + final boolean checkForUpdates = (boolean) nVal; defaultPreferences.edit() - .putBoolean(getString(R.string.update_app_key), (boolean) checkForUpdates).apply(); + .putBoolean(getString(R.string.update_app_key), checkForUpdates) + .apply(); - if ((boolean) checkForUpdates) { + if (checkForUpdates) { checkNewVersionNow(); } return true; }; - private final Preference.OnPreferenceClickListener manualUpdateClick - = preference -> { + private final Preference.OnPreferenceClickListener manualUpdateClick = preference -> { Toast.makeText(getContext(), R.string.checking_updates_toast, Toast.LENGTH_SHORT).show(); checkNewVersionNow(); return true; diff --git a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java index dc6e29d7d..68225fbab 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/DataReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/DataReader.java @@ -82,8 +82,8 @@ public class DataReader { public long readLong() throws IOException { primitiveRead(LONG_SIZE); - final long high - = primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; + final long high = + primitive[0] << 24 | primitive[1] << 16 | primitive[2] << 8 | primitive[3]; final long low = primitive[4] << 24 | primitive[5] << 16 | primitive[6] << 8 | primitive[7]; return high << 32 | low; } diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java index 889cc85e6..807f190b4 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java @@ -307,8 +307,8 @@ public class Mp4FromDashWriter { outWrite(makeMdat(totalSampleSize, is64)); final int[] sampleIndex = new int[readers.length]; - final int[] sizes - = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK]; + final int[] sizes = + new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK]; final int[] sync = new int[singleSampleBuffer > 0 ? singleSampleBuffer : SAMPLES_PER_CHUNK]; int written = readers.length; diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index eabac8330..b0b4bae74 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -176,8 +176,8 @@ public final class ListHelper { @Nullable final List videoOnlyStreams, final boolean ascendingOrder, final boolean preferVideoOnlyStreams) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); final boolean showHigherResolutions = preferences.getBoolean( context.getString(R.string.show_higher_resolutions_key), false); @@ -214,8 +214,8 @@ public final class ListHelper { private static String computeDefaultResolution(final Context context, final int key, final int value) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); // Load the preferred resolution otherwise the best available String resolution = preferences != null @@ -254,8 +254,8 @@ public final class ListHelper { return 0; } - final int defaultStreamIndex - = getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams); + final int defaultStreamIndex = + getVideoStreamIndex(defaultResolution, defaultFormat, videoStreams); // this is actually an error, // but maybe there is really no stream fitting to the default value. @@ -446,8 +446,9 @@ public final class ListHelper { final String targetResolutionNoRefresh = targetResolution.replaceAll("p\\d+$", "p"); for (int idx = 0; idx < videoStreams.size(); idx++) { - final MediaFormat format - = targetFormat == null ? null : videoStreams.get(idx).getFormat(); + final MediaFormat format = targetFormat == null + ? null + : videoStreams.get(idx).getFormat(); final String resolution = videoStreams.get(idx).getResolution(); final String resolutionNoRefresh = resolution.replaceAll("p\\d+$", "p"); @@ -510,8 +511,8 @@ public final class ListHelper { private static MediaFormat getDefaultFormat(@NonNull final Context context, @StringRes final int defaultFormatKey, @StringRes final int defaultFormatValueKey) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); final String defaultFormat = context.getString(defaultFormatValueKey); final String defaultFormatString = preferences.getString( @@ -617,8 +618,8 @@ public final class ListHelper { private static String getResolutionLimit(@NonNull final Context context) { String resolutionLimit = null; if (isMeteredNetwork(context)) { - final SharedPreferences preferences - = PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); final String defValue = context.getString(R.string.limit_data_usage_none_key); final String value = preferences.getString( context.getString(R.string.limit_mobile_data_usage_key), defValue); @@ -634,8 +635,8 @@ public final class ListHelper { * @return {@code true} if connected to a metered network */ public static boolean isMeteredNetwork(@NonNull final Context context) { - final ConnectivityManager manager - = ContextCompat.getSystemService(context, ConnectivityManager.class); + final ConnectivityManager manager = + ContextCompat.getSystemService(context, ConnectivityManager.class); if (manager == null || manager.getActiveNetworkInfo() == null) { return false; } diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java index dcc39eccf..97e6e7563 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java @@ -54,8 +54,8 @@ public final class PeertubeHelper { final Context context) { final SharedPreferences sharedPreferences = PreferenceManager .getDefaultSharedPreferences(context); - final String selectedInstanceKey - = context.getString(R.string.peertube_selected_instance_key); + final String selectedInstanceKey = + context.getString(R.string.peertube_selected_instance_key); final JsonStringWriter jsonWriter = JsonWriter.string().object(); jsonWriter.value("name", instance.getName()); jsonWriter.value("url", instance.getUrl()); diff --git a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java index 1e3061374..f3151ec8b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PermissionHelper.java @@ -122,8 +122,8 @@ public final class PermissionHelper { } public static void showPopupEnablementToast(final Context context) { - final Toast toast - = Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG); + final Toast toast = + Toast.makeText(context, R.string.msg_popup_permission, Toast.LENGTH_LONG); final TextView messageView = toast.getView().findViewById(android.R.id.message); if (messageView != null) { messageView.setGravity(Gravity.CENTER); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index a104660f4..54140b0fb 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -28,8 +28,8 @@ import okhttp3.OkHttpClient; public final class PicassoHelper { public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; - private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY - = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; + private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = + "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; private PicassoHelper() { } diff --git a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java index 6ebdaee02..3c901aacb 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java +++ b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java @@ -46,8 +46,8 @@ import java.util.concurrent.ConcurrentHashMap; */ public final class StateSaver { public static final String KEY_SAVED_STATE = "key_saved_state"; - private static final ConcurrentHashMap> STATE_OBJECTS_HOLDER - = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap> STATE_OBJECTS_HOLDER = + new ConcurrentHashMap<>(); private static final String TAG = "StateSaver"; private static final String CACHE_DIR_NAME = "state_cache"; private static String cacheDirPath; @@ -107,8 +107,8 @@ public final class StateSaver { } try { - Queue savedObjects - = STATE_OBJECTS_HOLDER.remove(savedState.getPrefixFileSaved()); + Queue savedObjects = + STATE_OBJECTS_HOLDER.remove(savedState.getPrefixFileSaved()); if (savedObjects != null) { writeRead.readFrom(savedObjects); if (MainActivity.DEBUG) { diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java index 240341ab0..c46e6636d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java +++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java @@ -153,13 +153,13 @@ public final class InternalUrlsHandler { return false; } - final Single single - = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false); + final Single single = + ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false); disposables.add(single.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(info -> { - final PlayQueue playQueue - = new SinglePlayQueue(info, seconds * 1000L); + final PlayQueue playQueue = + new SinglePlayQueue(info, seconds * 1000L); NavigationHelper.playOnPopupPlayer(context, playQueue, false); }, throwable -> { if (DEBUG) { diff --git a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java index 5a0dbb003..5fededbcb 100644 --- a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java +++ b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java @@ -18,159 +18,13 @@ package org.schabi.newpipe.util.urlfinder; -import androidx.annotation.RestrictTo; - import java.util.regex.Pattern; -import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; - /** * Commonly used regular expression patterns. */ public final class PatternsCompat { - /** - * Regular expression to match all IANA top-level domains. - * - * List accurate as of 2015/11/24. List taken from: - * http://data.iana.org/TLD/tlds-alpha-by-domain.txt - * This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py - */ - static final String IANA_TOP_LEVEL_DOMAINS = "(?:" - + "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active" - + "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica" - + "|amsterdam|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia" - + "|associates|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])" - + "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva" - + "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz" - + "|black|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots" - + "|boutique|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build" - + "|builders|business|buzz|bzh|b[abdefghijmnorstvwyz])" - + "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards" - + "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center" - + "|ceo|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani" - + "|cisco|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed" - + "|coach|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec" - + "|condos|construction|consulting|contractors|cooking|cool|coop|corsica|country" - + "|coupons|courses|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc" - + "|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])" - + "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta" - + "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory" - + "|discount|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])" - + "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises" - + "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert" - + "|exposed|express|e[cegrstu])" - + "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm" - + "|fashion|feedback|ferrero|film|final|finance|financial|firmdale|fish|fishing|fit" - + "|fitness|flights|florist|flowers|flsmidth|fly|foo|football|forex|forsale|forum" - + "|foundation|frl|frogans|fund|furniture|futbol|fyi|f[ijkmor])" - + "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving" - + "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov" - + "|grainger|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru" - + "|g[abdefghilmnpqrstuwy])" - + "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey" - + "|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house" - + "|how|hsbc|hyundai|h[kmnrtu])" - + "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink" - + "|institute|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau" - + "|iwc|i[delmnoqrst])" - + "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])" - + "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto" - + "|k[eghimnprwyz])" - + "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease" - + "|leclerc|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde" - + "|link|live|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury" - + "|l[abcikrstuvy])" - + "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba" - + "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi" - + "|moda|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov" - + "|movie|movistar|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])" - + "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk" - + "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])" - + "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka" - + "|otsuka|ovh|om)" - + "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography" - + "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation" - + "|plumbing|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties" - + "|property|protection|pub|p[aefghklmnrstwy])" - + "|(?:qpon|quebec|qa)" - + "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent" - + "|rentals|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip" - + "|rocher|rocks|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])" - + "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo" - + "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat" - + "|security|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles" - + "|site|ski|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space" - + "|spiegel|spreadbetting|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study" - + "|style|sucks|supplies|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems" - + "|s[abcdeghijklmnortuvxyz])" - + "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica" - + "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo" - + "|tools|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust" - + "|tui|t[cdfghjklmnortvwz])" - + "|(?:ubs|university|uno|uol|u[agksyz])" - + "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin" - + "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])" - + "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki" - + "|williamhill|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])" - + "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c" - + "|\u043c\u043a\u0434|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430" - + "|\u043e\u043d\u043b\u0430\u0439\u043d|\u043e\u0440\u0433|\u0440\u0443\u0441" - + "|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431|\u0443\u043a\u0440" - + "|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd" - + "|\u0627\u0631\u0627\u0645\u0643\u0648|\u0627\u0644\u0627\u0631\u062f\u0646" - + "|\u0627\u0644\u062c\u0632\u0627\u0626\u0631" - + "|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629" - + "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a" - + "|\u0627\u06cc\u0631\u0627\u0646|\u0628\u0627\u0632\u0627\u0631" - + "|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633" - + "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629" - + "|\u0634\u0628\u0643\u0629|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646" - + "|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0643\u0648\u0645" - + "|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627|\u0645\u0648\u0642\u0639" - + "|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924" - + "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24" - + "|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe" - + "|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8" - + "|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd" - + "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21" - + "|\u0e44\u0e17\u0e22|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb" - + "|\u30b3\u30e0|\u4e16\u754c|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51" - + "|\u4f01\u4e1a|\u4f5b\u5c71|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8" - + "|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063|\u5546\u57ce|\u5546\u5e97|\u5546\u6807" - + "|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c|\u5e7f\u4e1c|\u6148\u5584" - + "|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c|\u65b0\u52a0\u5761" - + "|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f|\u70b9\u770b" - + "|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc" - + "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137" - + "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox|xerox|xin|xn\\-\\-11b4c3d" - + "|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g|xn\\-\\-3e0b707e" - + "|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim" - + "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks" - + "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais" - + "|xn\\-\\-9dbq2a|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g" - + "|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-czr694b|xn\\-\\-czrs0t" - + "|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h|xn\\-\\-estv75g" - + "|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s" - + "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c" - + "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i" - + "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d" - + "|xn\\-\\-kpry57d|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf" - + "|xn\\-\\-mgba3a3ejt|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd" - + "|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar" - + "|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m" - + "|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema|xn\\-\\-nyqy26a" - + "|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh" - + "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g" - + "|xn\\-\\-s9brj9c|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y" - + "|xn\\-\\-vermgensberater\\-ctb|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv" - + "|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a" - + "|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx" - + "|xn\\-\\-zfr164b|xperia|xxx|xyz)" - + "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])" - + "|(?:zara|zip|zone|zuerich|z[amw]))"; - - public static final Pattern IP_ADDRESS - = Pattern.compile( + public static final Pattern IP_ADDRESS = Pattern.compile( "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" @@ -204,28 +58,11 @@ public final class PatternsCompat { */ private static final String LABEL_CHAR = "a-zA-Z0-9" + UCS_CHAR; - /** - * Valid characters for IRI TLD defined in RFC 3987. - */ - private static final String TLD_CHAR = "a-zA-Z" + UCS_CHAR; - /** * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets. */ - private static final String IRI_LABEL - = "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}"; - - /** - * RFC 3492 references RFC 1034 and limits Punycode algorithm output to 63 characters. - */ - private static final String PUNYCODE_TLD = "xn\\-\\-[\\w\\-]{0,58}\\w"; - - private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" + ")"; - - private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD; - - public static final Pattern DOMAIN_NAME - = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")"); + private static final String IRI_LABEL = + "[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "_\\-]{0,61}[" + LABEL_CHAR + "]){0,1}"; //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // CHANGED: Removed rtsp from supported protocols // @@ -245,59 +82,11 @@ public final class PatternsCompat { + ";/\\?:@&=#~" // plus optional query params + "\\-\\.\\+!\\*'\\(\\),_\\$])|(?:%[a-fA-F0-9]{2}))*"; - /** - * Regular expression pattern to match most part of RFC 3987 - * Internationalized URLs, aka IRIs. - */ - public static final Pattern WEB_URL = Pattern.compile("(" - + "(" - + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")?" - + "(?:" + DOMAIN_NAME + ")" - + "(?:" + PORT_NUMBER + ")?" - + ")" - + "(" + PATH_AND_QUERY + ")?" - + WORD_BOUNDARY - + ")"); - - /** - * Regular expression that matches known TLDs and punycode TLDs. - */ - private static final String STRICT_TLD = "(?:" - + IANA_TOP_LEVEL_DOMAINS + "|" + PUNYCODE_TLD + ")"; - - /** - * Regular expression that matches host names using {@link #STRICT_TLD}. - */ - private static final String STRICT_HOST_NAME = "(?:(?:" + IRI_LABEL + "\\.)+" - + STRICT_TLD + ")"; - - /** - * Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or - * {@link #IP_ADDRESS}. - */ - private static final Pattern STRICT_DOMAIN_NAME - = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")"); - /** * Regular expression that matches domain names without a TLD. */ - private static final String RELAXED_DOMAIN_NAME - = "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" + "?)+" + "|" + IP_ADDRESS + ")"; - - /** - * Regular expression to match strings that do not start with a supported protocol. The TLDs - * are expected to be one of the known TLDs. - */ - private static final String WEB_URL_WITHOUT_PROTOCOL = "(" - + WORD_BOUNDARY - + "(? Date: Fri, 15 Jul 2022 19:49:41 +0200 Subject: [PATCH 218/992] Added note that explains that unused code was removed. --- .../org/schabi/newpipe/util/urlfinder/PatternsCompat.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java index 5fededbcb..49be86ae0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java +++ b/app/src/main/java/org/schabi/newpipe/util/urlfinder/PatternsCompat.java @@ -24,6 +24,10 @@ import java.util.regex.Pattern; * Commonly used regular expression patterns. */ public final class PatternsCompat { + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // CHANGED: Removed unused code // + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + public static final Pattern IP_ADDRESS = Pattern.compile( "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" From 3ba04f179fb3d6b5ad9505ed663d1e09b0579cd3 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Fri, 15 Jul 2022 19:53:48 +0200 Subject: [PATCH 219/992] Fixed conflicts/build --- .../org/schabi/newpipe/DownloaderImpl.java | 2 - .../fragments/detail/VideoDetailFragment.java | 5 ++- .../notification/NotificationConstants.java | 39 ++++++++++--------- .../newpipe/player/ui/MainPlayerUi.java | 18 +++++---- .../newpipe/player/ui/VideoPlayerUi.java | 7 ++-- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 3579a0bad..74ca39e12 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -1,7 +1,5 @@ package org.schabi.newpipe; -import static org.schabi.newpipe.MainActivity.DEBUG; - import android.content.Context; import androidx.annotation.NonNull; 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 a24f667a7..55a6f5d5b 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 @@ -1555,8 +1555,8 @@ public final class VideoDetailFragment binding.detailUploaderThumbnailView.setVisibility(View.GONE); } - final Drawable buddyDrawable - = AppCompatResources.getDrawable(activity, R.drawable.placeholder_person); + final Drawable buddyDrawable = + AppCompatResources.getDrawable(activity, R.drawable.placeholder_person); binding.detailSubChannelThumbnailView.setImageDrawable(buddyDrawable); binding.detailUploaderThumbnailView.setImageDrawable(buddyDrawable); @@ -2030,6 +2030,7 @@ public final class VideoDetailFragment } // Listener implementation + @Override public void hideSystemUiIfNeeded() { if (isFullscreen() && bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index 53ef752bd..b8e39e564 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -30,25 +30,26 @@ public final class NotificationConstants { // Intent actions //////////////////////////////////////////////////////////////////////////*/ - public static final String ACTION_CLOSE - = App.PACKAGE_NAME + ".player.MainPlayer.CLOSE"; - public static final String ACTION_PLAY_PAUSE - = App.PACKAGE_NAME + ".player.MainPlayer.PLAY_PAUSE"; - public static final String ACTION_REPEAT - = App.PACKAGE_NAME + ".player.MainPlayer.REPEAT"; - public static final String ACTION_PLAY_NEXT - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_NEXT"; - public static final String ACTION_PLAY_PREVIOUS - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; - public static final String ACTION_FAST_REWIND - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_REWIND"; - public static final String ACTION_FAST_FORWARD - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_FAST_FORWARD"; - public static final String ACTION_SHUFFLE - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_SHUFFLE"; - public static final String ACTION_RECREATE_NOTIFICATION - = App.PACKAGE_NAME + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; - + private static final String BASE_ACTION = + App.PACKAGE_NAME + ".player.MainPlayer."; + public static final String ACTION_CLOSE = + BASE_ACTION + "CLOSE"; + public static final String ACTION_PLAY_PAUSE = + BASE_ACTION + ".player.MainPlayer.PLAY_PAUSE"; + public static final String ACTION_REPEAT = + BASE_ACTION + ".player.MainPlayer.REPEAT"; + public static final String ACTION_PLAY_NEXT = + BASE_ACTION + ".player.MainPlayer.ACTION_PLAY_NEXT"; + public static final String ACTION_PLAY_PREVIOUS = + BASE_ACTION + ".player.MainPlayer.ACTION_PLAY_PREVIOUS"; + public static final String ACTION_FAST_REWIND = + BASE_ACTION + ".player.MainPlayer.ACTION_FAST_REWIND"; + public static final String ACTION_FAST_FORWARD = + BASE_ACTION + ".player.MainPlayer.ACTION_FAST_FORWARD"; + public static final String ACTION_SHUFFLE = + BASE_ACTION + ".player.MainPlayer.ACTION_SHUFFLE"; + public static final String ACTION_RECREATE_NOTIFICATION = + BASE_ACTION + ".player.MainPlayer.ACTION_RECREATE_NOTIFICATION"; public static final int NOTHING = 0; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java index 52a486add..81dc954d1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.java @@ -534,8 +534,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh binding.brightnessProgressBar.setMax(maxGestureLength); setInitialGestureValues(); - binding.itemsListPanel.getLayoutParams().height - = height - binding.itemsListPanel.getTop(); + binding.itemsListPanel.getLayoutParams().height = + height - binding.itemsListPanel.getTop(); } } @@ -710,8 +710,9 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh } int nearestPosition = 0; - final List segments - = player.getCurrentStreamInfo().get().getStreamSegments(); + final List segments = player.getCurrentStreamInfo() + .get() + .getStreamSegments(); for (int i = 0; i < segments.size(); i++) { if (segments.get(i).getStartTimeSeconds() * 1000L > playbackPosition) { @@ -912,8 +913,8 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh if (DEBUG) { Log.d(TAG, "toggleFullscreen() called"); } - final PlayerServiceEventListener fragmentListener - = player.getFragmentListener().orElse(null); + final PlayerServiceEventListener fragmentListener = player.getFragmentListener() + .orElse(null); if (fragmentListener == null || player.exoPlayerIsNull()) { return; } @@ -939,8 +940,9 @@ public final class MainPlayerUi extends VideoPlayerUi implements View.OnLayoutCh public void checkLandscape() { // check if landscape is correct - final boolean videoInLandscapeButNotInFullscreen - = isLandscape() && !isFullscreen && !player.isAudioOnly(); + final boolean videoInLandscapeButNotInFullscreen = isLandscape() + && !isFullscreen + && !player.isAudioOnly(); final boolean notPaused = player.getCurrentState() != STATE_COMPLETED && player.getCurrentState() != STATE_PAUSED; diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index d38c8cfe4..217dd38b3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -226,8 +226,8 @@ public abstract class VideoPlayerUi extends PlayerUi // PlaybackControlRoot already consumed window insets but we should pass them to // player_overlays and fast_seek_overlay too. Without it they will be off-centered. - onLayoutChangeListener - = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + onLayoutChangeListener = + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { binding.playerOverlays.setPadding( v.getPaddingLeft(), v.getPaddingTop(), @@ -1053,8 +1053,7 @@ public abstract class VideoPlayerUi extends PlayerUi } qualityPopupMenu.getMenu().removeGroup(POPUP_MENU_ID_QUALITY); - @Nullable final List availableStreams - = Optional.ofNullable(player.getCurrentMetadata()) + final List availableStreams = Optional.ofNullable(player.getCurrentMetadata()) .flatMap(MediaItemTag::getMaybeQuality) .map(MediaItemTag.Quality::getSortedVideoStreams) .orElse(null); From 5da8d5fc7324be346ea9f431aeb74e7ad76b8702 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 16 Jul 2022 05:47:57 +0530 Subject: [PATCH 220/992] Use ViewCompat.setBackgroundTintList(). --- app/src/main/java/org/schabi/newpipe/ktx/View.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 ea680dd60..56c9d825a 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -12,6 +12,7 @@ import android.view.View import androidx.annotation.ColorInt import androidx.annotation.FloatRange import androidx.core.animation.addListener +import androidx.core.view.ViewCompat import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -101,11 +102,11 @@ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @Colo viewPropertyAnimator.interpolator = FastOutSlowInInterpolator() viewPropertyAnimator.duration = duration viewPropertyAnimator.addUpdateListener { animation: ValueAnimator -> - backgroundTintList = ColorStateList(empty, intArrayOf(animation.animatedValue as Int)) + ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(animation.animatedValue as Int))) } viewPropertyAnimator.addListener( - onCancel = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) }, - onEnd = { backgroundTintList = ColorStateList(empty, intArrayOf(colorEnd)) } + onCancel = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) }, + onEnd = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) } ) viewPropertyAnimator.start() } From f1de353b74f10e6834bf3e2ea33cd784b31a0654 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 16 Jul 2022 08:34:04 +0530 Subject: [PATCH 221/992] Use stream sorting. --- .../database/playlist/PlaylistLocalItem.java | 17 ++++++----------- .../fragments/detail/DescriptionFragment.java | 7 +++---- 2 files changed, 9 insertions(+), 15 deletions(-) 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 43dbd89ea..695f9ec5a 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 @@ -3,10 +3,10 @@ package org.schabi.newpipe.database.playlist; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; -import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; public interface PlaylistLocalItem extends LocalItem { String getOrderingName(); @@ -14,14 +14,9 @@ public interface PlaylistLocalItem extends LocalItem { static List merge( final List localPlaylists, final List remotePlaylists) { - final List items = new ArrayList<>( - localPlaylists.size() + remotePlaylists.size()); - items.addAll(localPlaylists); - items.addAll(remotePlaylists); - - Collections.sort(items, Comparator.comparing(PlaylistLocalItem::getOrderingName, - Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER))); - - return items; + return Stream.concat(localPlaylists.stream(), remotePlaylists.stream()) + .sorted(Comparator.comparing(PlaylistLocalItem::getOrderingName, + Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER))) + .collect(Collectors.toList()); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index d57ddb02d..362388314 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -26,9 +26,8 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.TextLinkifier; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import icepick.State; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -209,8 +208,8 @@ public class DescriptionFragment extends BaseFragment { final ItemMetadataTagsBinding itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - final List tags = new ArrayList<>(streamInfo.getTags()); - Collections.sort(tags); + final List tags = streamInfo.getTags().stream().sorted() + .collect(Collectors.toList()); for (final String tag : tags) { final Chip chip = (Chip) inflater.inflate(R.layout.chip, itemBinding.metadataTagsChips, false); From a1c6f0073e196a9c742a2addedddb9f79ea7e7f4 Mon Sep 17 00:00:00 2001 From: Mohammed Anas Date: Sun, 17 Jul 2022 20:37:15 +0300 Subject: [PATCH 222/992] Rename "waiting-for-author" label to "waiting for author" (#8642) --- .github/workflows/no-response.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index 6a4a8a61a..b3495135f 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -21,4 +21,4 @@ jobs: with: token: ${{ github.token }} daysUntilClose: 14 - responseRequiredLabel: waiting-for-author + responseRequiredLabel: waiting for author From e8669d4ab50e323c2353c908fd1c9660d8cf47d6 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 13 Jul 2022 11:37:46 +0200 Subject: [PATCH 223/992] Deduplicate SQL queries to get feed streams --- .../newpipe/database/feed/dao/FeedDAO.kt | 118 +++++------------- .../newpipe/local/feed/FeedDatabaseManager.kt | 20 ++- .../newpipe/local/feed/FeedViewModel.kt | 3 +- 3 files changed, 37 insertions(+), 104 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt index d573788a6..b2b3d18a6 100644 --- a/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt +++ b/app/src/main/java/org/schabi/newpipe/database/feed/dao/FeedDAO.kt @@ -9,6 +9,7 @@ import androidx.room.Update import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import org.schabi.newpipe.database.feed.model.FeedEntity +import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity import org.schabi.newpipe.database.stream.StreamWithState import org.schabi.newpipe.database.stream.model.StreamStateEntity @@ -21,56 +22,16 @@ abstract class FeedDAO { @Query("DELETE FROM feed") abstract fun deleteAll(): Int - @Query( - """ - SELECT s.*, sst.progress_time - FROM streams s - - LEFT JOIN stream_state sst - ON s.uid = sst.stream_id - - LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - - INNER JOIN feed f - ON s.uid = f.stream_id - - ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC - LIMIT 500 - """ - ) - abstract fun getAllStreams(): Maybe> - - @Query( - """ - SELECT s.*, sst.progress_time - FROM streams s - - LEFT JOIN stream_state sst - ON s.uid = sst.stream_id - - LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - - INNER JOIN feed f - ON s.uid = f.stream_id - - INNER JOIN feed_group_subscription_join fgs - ON fgs.subscription_id = f.subscription_id - - WHERE fgs.group_id = :groupId - - ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC - LIMIT 500 - """ - ) - abstract fun getAllStreamsForGroup(groupId: Long): Maybe> - /** + * @param groupId the group id to get feed streams of; use + * [FeedGroupEntity.GROUP_ALL_ID] to not filter by group + * @param includePlayed if false, only return all of the live, never-played or non-finished + * feed streams (see `@see` items); if true no filter is applied + * @param uploadDateBefore get only streams uploaded before this date (useful to filter out + * future streams); use null to not filter by upload date + * @return the feed streams filtered according to the conditions provided in the parameters * @see StreamStateEntity.isFinished() * @see StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS - * @return all of the non-live, never-played and non-finished streams in the feed - * (all of the cited conditions must hold for a stream to be in the returned list) */ @Query( """ @@ -79,67 +40,44 @@ abstract class FeedDAO { LEFT JOIN stream_state sst ON s.uid = sst.stream_id - + LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - + ON s.uid = sh.stream_id + INNER JOIN feed f ON s.uid = f.stream_id + LEFT JOIN feed_group_subscription_join fgs + ON fgs.subscription_id = f.subscription_id + WHERE ( - sh.stream_id IS NULL - OR sst.stream_id IS NULL - OR sst.progress_time < s.duration * 1000 - ${StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS} - OR sst.progress_time < s.duration * 1000 * 3 / 4 - OR s.stream_type = 'LIVE_STREAM' - OR s.stream_type = 'AUDIO_LIVE_STREAM' + :groupId = ${FeedGroupEntity.GROUP_ALL_ID} + OR fgs.group_id = :groupId ) - - ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC - LIMIT 500 - """ - ) - abstract fun getLiveOrNotPlayedStreams(): Maybe> - - /** - * @see StreamStateEntity.isFinished() - * @see StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS - * @param groupId the group id to get streams of - * @return all of the non-live, never-played and non-finished streams for the given feed group - * (all of the cited conditions must hold for a stream to be in the returned list) - */ - @Query( - """ - SELECT s.*, sst.progress_time - FROM streams s - - LEFT JOIN stream_state sst - ON s.uid = sst.stream_id - - LEFT JOIN stream_history sh - ON s.uid = sh.stream_id - - INNER JOIN feed f - ON s.uid = f.stream_id - - INNER JOIN feed_group_subscription_join fgs - ON fgs.subscription_id = f.subscription_id - - WHERE fgs.group_id = :groupId AND ( - sh.stream_id IS NULL + :includePlayed + OR sh.stream_id IS NULL OR sst.stream_id IS NULL OR sst.progress_time < s.duration * 1000 - ${StreamStateEntity.PLAYBACK_FINISHED_END_MILLISECONDS} OR sst.progress_time < s.duration * 1000 * 3 / 4 OR s.stream_type = 'LIVE_STREAM' OR s.stream_type = 'AUDIO_LIVE_STREAM' ) + AND ( + :uploadDateBefore IS NULL + OR s.upload_date IS NULL + OR s.upload_date < :uploadDateBefore + ) ORDER BY s.upload_date IS NULL DESC, s.upload_date DESC, s.uploader ASC LIMIT 500 """ ) - abstract fun getLiveOrNotPlayedStreamsForGroup(groupId: Long): Maybe> + abstract fun getStreams( + groupId: Long, + includePlayed: Boolean, + uploadDateBefore: OffsetDateTime? + ): Maybe> @Query( """ diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt index 7a8723ceb..07edb0499 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedDatabaseManager.kt @@ -41,19 +41,15 @@ class FeedDatabaseManager(context: Context) { fun database() = database fun getStreams( - groupId: Long = FeedGroupEntity.GROUP_ALL_ID, - getPlayedStreams: Boolean = true + groupId: Long, + includePlayedStreams: Boolean, + includeFutureStreams: Boolean ): Maybe> { - return when (groupId) { - FeedGroupEntity.GROUP_ALL_ID -> { - if (getPlayedStreams) feedTable.getAllStreams() - else feedTable.getLiveOrNotPlayedStreams() - } - else -> { - if (getPlayedStreams) feedTable.getAllStreamsForGroup(groupId) - else feedTable.getLiveOrNotPlayedStreamsForGroup(groupId) - } - } + return feedTable.getStreams( + groupId, + includePlayedStreams, + if (includeFutureStreams) null else OffsetDateTime.now() + ) } fun outdatedSubscriptions(outdatedThreshold: OffsetDateTime) = feedTable.getAllOutdated(outdatedThreshold) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index 87409ddae..7f5ef4301 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -65,9 +65,8 @@ class FeedViewModel( .map { (event, showPlayedItems, showFutureItems, notLoadedCount, oldestUpdate) -> val streamItems = if (event is SuccessResultEvent || event is IdleEvent) feedDatabaseManager - .getStreams(groupId, showPlayedItems) + .getStreams(groupId, showPlayedItems, showFutureItems) .blockingGet(arrayListOf()) - .filter { s -> showFutureItems || s.stream.uploadDate?.isBefore(OffsetDateTime.now()) ?: true } else arrayListOf() From e77224444051323fe506b350ebd4df5546ca4de2 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne <31027858+Isira-Seneviratne@users.noreply.github.com> Date: Tue, 19 Jul 2022 05:10:51 +0530 Subject: [PATCH 224/992] Update app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java Co-authored-by: Stypox --- .../newpipe/fragments/detail/DescriptionFragment.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index 362388314..ab4883768 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -26,9 +26,6 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; import org.schabi.newpipe.util.external_communication.TextLinkifier; -import java.util.List; -import java.util.stream.Collectors; - import icepick.State; import io.reactivex.rxjava3.disposables.CompositeDisposable; @@ -208,16 +205,14 @@ public class DescriptionFragment extends BaseFragment { final ItemMetadataTagsBinding itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - final List tags = streamInfo.getTags().stream().sorted() - .collect(Collectors.toList()); - for (final String tag : tags) { + streamInfo.getTags().stream().sorted().forEach(tag -> { final Chip chip = (Chip) inflater.inflate(R.layout.chip, itemBinding.metadataTagsChips, false); chip.setText(tag); chip.setOnClickListener(this::onTagClick); chip.setOnLongClickListener(this::onTagLongClick); itemBinding.metadataTagsChips.addView(chip); - } + }); layout.addView(itemBinding.getRoot()); } From c53143ef4fe86b4ca43f8b8828c3b4eacae35acd Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 15 Jul 2022 07:40:25 +0530 Subject: [PATCH 225/992] Use Set.of(). --- app/src/main/java/org/schabi/newpipe/util/CookieUtils.java | 3 +-- app/src/main/java/org/schabi/newpipe/util/ListHelper.java | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java index d970ec472..b906c1c4f 100644 --- a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java @@ -2,7 +2,6 @@ package org.schabi.newpipe.util; import android.text.TextUtils; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -20,6 +19,6 @@ public final class CookieUtils { } public static Set splitCookies(final String cookies) { - return new HashSet<>(Arrays.asList(cookies.split("; *"))); + return Set.of(cookies.split("; *")); } } diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index b0b4bae74..9707956ed 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -22,7 +22,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -40,9 +39,8 @@ public final class ListHelper { // Audio format in order of efficiency. 0=most efficient, n=least efficient private static final List AUDIO_FORMAT_EFFICIENCY_RANKING = Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); - // Use a HashSet for better performance - private static final Set HIGH_RESOLUTION_LIST = new HashSet<>( - Arrays.asList("1440p", "2160p")); + // Use a Set for better performance + private static final Set HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p"); private ListHelper() { } From 4eddd2c3d1e246d92e8da81e335c3dc10fc1e68c Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 19 Jul 2022 20:01:46 +0200 Subject: [PATCH 226/992] Fix random NullPointerException when adding video player view --- .../newpipe/fragments/detail/VideoDetailFragment.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 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 55a6f5d5b..cc8007596 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 @@ -1313,9 +1313,11 @@ public final class VideoDetailFragment // Prevent from re-adding a view multiple times new Handler(Looper.getMainLooper()).post(() -> player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { - playerUi.removeViewFromParent(); - binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); - playerUi.setupVideoSurfaceIfNeeded(); + if (binding != null) { + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + } })); } From ca26fcb0ebf906d10cdc9551e1043ee074fc88c1 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 15 Jul 2022 07:40:29 +0530 Subject: [PATCH 227/992] Use List.of(). --- .../material/appbar/FlingBehavior.java | 5 +- app/src/main/java/org/schabi/newpipe/App.java | 65 +++++++++---------- .../org/schabi/newpipe/QueueItemMenuUtil.java | 4 +- .../org/schabi/newpipe/RouterActivity.java | 3 +- .../fragments/detail/VideoDetailFragment.java | 3 +- .../dialog/StreamDialogDefaultEntry.java | 4 +- .../gesture/CustomBottomSheetBehavior.java | 5 +- .../player/mediasource/FailedMediaSource.java | 6 +- .../newpipe/player/playqueue/PlayQueue.java | 3 +- .../player/playqueue/SinglePlayQueue.java | 7 +- .../PreferenceSearchConfiguration.java | 8 +-- .../PreferenceSearchItem.java | 7 +- .../newpipe/settings/tabs/TabsJsonHelper.java | 10 +-- .../org/schabi/newpipe/util/ListHelper.java | 6 +- .../schabi/newpipe/util/PeertubeHelper.java | 6 +- .../player/playqueue/PlayQueueTest.java | 2 +- .../schabi/newpipe/util/ListHelperTest.java | 33 +++++----- 17 files changed, 75 insertions(+), 102 deletions(-) diff --git a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java index 47c8deb83..52754e8fa 100644 --- a/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java +++ b/app/src/main/java/com/google/android/material/appbar/FlingBehavior.java @@ -14,7 +14,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout; import org.schabi.newpipe.R; import java.lang.reflect.Field; -import java.util.Arrays; import java.util.List; // See https://stackoverflow.com/questions/56849221#57997489 @@ -27,7 +26,7 @@ public final class FlingBehavior extends AppBarLayout.Behavior { private boolean allowScroll = true; private final Rect globalRect = new Rect(); - private final List skipInterceptionOfElements = Arrays.asList( + private final List skipInterceptionOfElements = List.of( R.id.itemsListPanel, R.id.playbackSeekBar, R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton); @@ -67,7 +66,7 @@ public final class FlingBehavior extends AppBarLayout.Behavior { public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent, @NonNull final AppBarLayout child, @NonNull final MotionEvent ev) { - for (final Integer element : skipInterceptionOfElements) { + for (final int element : skipInterceptionOfElements) { final View view = child.findViewById(element); if (view != null) { final boolean visible = view.getGlobalVisibleRect(globalRect); diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index c63f07891..f4410a31b 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -27,9 +27,8 @@ import org.schabi.newpipe.util.StateSaver; import java.io.IOException; import java.io.InterruptedIOException; import java.net.SocketException; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Objects; import io.reactivex.rxjava3.exceptions.CompositeException; import io.reactivex.rxjava3.exceptions.MissingBackpressureException; @@ -140,7 +139,7 @@ public class App extends Application { if (throwable instanceof UndeliverableException) { // As UndeliverableException is a wrapper, // get the cause of it to get the "real" exception - actualThrowable = throwable.getCause(); + actualThrowable = Objects.requireNonNull(throwable.getCause()); } else { actualThrowable = throwable; } @@ -149,7 +148,7 @@ public class App extends Application { if (actualThrowable instanceof CompositeException) { errors = ((CompositeException) actualThrowable).getExceptions(); } else { - errors = Collections.singletonList(actualThrowable); + errors = List.of(actualThrowable); } for (final Throwable error : errors) { @@ -213,41 +212,37 @@ public class App extends Application { private void initNotificationChannels() { // Keep the importance below DEFAULT to avoid making noise on every notification update for // the main and update channels - final List notificationChannelCompats = new ArrayList<>(); - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.notification_channel_id), + final List notificationChannelCompats = List.of( + new NotificationChannelCompat.Builder(getString(R.string.notification_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) - .setName(getString(R.string.notification_channel_name)) - .setDescription(getString(R.string.notification_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.app_update_notification_channel_id), + .setName(getString(R.string.notification_channel_name)) + .setDescription(getString(R.string.notification_channel_description)) + .build(), + new NotificationChannelCompat + .Builder(getString(R.string.app_update_notification_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) - .setName(getString(R.string.app_update_notification_channel_name)) - .setDescription(getString(R.string.app_update_notification_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.hash_channel_id), + .setName(getString(R.string.app_update_notification_channel_name)) + .setDescription( + getString(R.string.app_update_notification_channel_description)) + .build(), + new NotificationChannelCompat.Builder(getString(R.string.hash_channel_id), NotificationManagerCompat.IMPORTANCE_HIGH) - .setName(getString(R.string.hash_channel_name)) - .setDescription(getString(R.string.hash_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.error_report_channel_id), + .setName(getString(R.string.hash_channel_name)) + .setDescription(getString(R.string.hash_channel_description)) + .build(), + new NotificationChannelCompat.Builder(getString(R.string.error_report_channel_id), NotificationManagerCompat.IMPORTANCE_LOW) - .setName(getString(R.string.error_report_channel_name)) - .setDescription(getString(R.string.error_report_channel_description)) - .build()); - - notificationChannelCompats.add(new NotificationChannelCompat - .Builder(getString(R.string.streams_notification_channel_id), - NotificationManagerCompat.IMPORTANCE_DEFAULT) - .setName(getString(R.string.streams_notification_channel_name)) - .setDescription(getString(R.string.streams_notification_channel_description)) - .build()); + .setName(getString(R.string.error_report_channel_name)) + .setDescription(getString(R.string.error_report_channel_description)) + .build(), + new NotificationChannelCompat + .Builder(getString(R.string.streams_notification_channel_id), + NotificationManagerCompat.IMPORTANCE_DEFAULT) + .setName(getString(R.string.streams_notification_channel_name)) + .setDescription( + getString(R.string.streams_notification_channel_description)) + .build() + ); final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); notificationManager.createNotificationChannelsCompat(notificationChannelCompats); diff --git a/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java b/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java index c7604e512..7c646d0e4 100644 --- a/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java +++ b/app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java @@ -16,7 +16,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.SparseItemUtil; -import java.util.Collections; +import java.util.List; public final class QueueItemMenuUtil { private QueueItemMenuUtil() { @@ -53,7 +53,7 @@ public final class QueueItemMenuUtil { case R.id.menu_item_append_playlist: PlaylistDialog.createCorrespondingDialog( context, - Collections.singletonList(new StreamEntity(item)), + List.of(new StreamEntity(item)), dialog -> dialog.show( fragmentManager, "QueueItemMenuUtil@append_playlist" diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index d055da1e8..522f25df8 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -81,7 +81,6 @@ import org.schabi.newpipe.views.FocusOverlayView; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import icepick.Icepick; @@ -649,7 +648,7 @@ public class RouterActivity extends AppCompatActivity { .subscribe( info -> PlaylistDialog.createCorrespondingDialog( getThemeWrapperContext(), - Collections.singletonList(new StreamEntity(info)), + List.of(new StreamEntity(info)), playlistDialog -> { playlistDialog.setOnDismissListener(dialog -> finish()); 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 55a6f5d5b..f4838482b 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 @@ -114,7 +114,6 @@ import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -453,7 +452,7 @@ public final class VideoDetailFragment disposables.add( PlaylistDialog.createCorrespondingDialog( getContext(), - Collections.singletonList(new StreamEntity(currentInfo)), + List.of(new StreamEntity(currentInfo)), dialog -> dialog.show(getFM(), TAG) ) ); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java index 2b3f73926..1265e9767 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/dialog/StreamDialogDefaultEntry.java @@ -20,7 +20,7 @@ import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.KoreUtils; import org.schabi.newpipe.util.external_communication.ShareUtils; -import java.util.Collections; +import java.util.List; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; @@ -89,7 +89,7 @@ public enum StreamDialogDefaultEntry { APPEND_PLAYLIST(R.string.add_to_playlist, (fragment, item) -> PlaylistDialog.createCorrespondingDialog( fragment.getContext(), - Collections.singletonList(new StreamEntity(item)), + List.of(new StreamEntity(item)), dialog -> dialog.show( fragment.getParentFragmentManager(), "StreamDialogEntry@" diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java index 240009105..41046784f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java @@ -14,7 +14,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior; import org.schabi.newpipe.R; -import java.util.Arrays; import java.util.List; public class CustomBottomSheetBehavior extends BottomSheetBehavior { @@ -25,7 +24,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior Rect globalRect = new Rect(); private boolean skippingInterception = false; - private final List skipInterceptionOfElements = Arrays.asList( + private final List skipInterceptionOfElements = List.of( R.id.detail_content_root_layout, R.id.relatedItemsLayout, R.id.itemsListPanel, R.id.view_pager, R.id.tab_layout, R.id.bottomControls, R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton); @@ -57,7 +56,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior if (getState() == BottomSheetBehavior.STATE_EXPANDED && event.getAction() == MotionEvent.ACTION_DOWN) { // Without overriding scrolling will not work when user touches these elements - for (final Integer element : skipInterceptionOfElements) { + for (final int element : skipInterceptionOfElements) { final View view = child.findViewById(element); if (view != null) { final boolean visible = view.getGlobalVisibleRect(globalRect); diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java index 8aad356d0..b9ca90d89 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java @@ -16,7 +16,7 @@ import org.schabi.newpipe.player.mediaitem.ExceptionTag; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import java.io.IOException; -import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; import androidx.annotation.NonNull; @@ -56,9 +56,7 @@ public class FailedMediaSource extends BaseMediaSource implements ManagedMediaSo this.playQueueItem = playQueueItem; this.error = error; this.retryTimestamp = retryTimestamp; - this.mediaItem = ExceptionTag - .of(playQueueItem, Collections.singletonList(error)) - .withExtras(this) + this.mediaItem = ExceptionTag.of(playQueueItem, List.of(error)).withExtras(this) .asMediaItem(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index f46c9d72f..d54fed248 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -16,7 +16,6 @@ import org.schabi.newpipe.player.playqueue.events.SelectEvent; import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -264,7 +263,7 @@ public abstract class PlayQueue implements Serializable { * @param items {@link PlayQueueItem}s to append */ public synchronized void append(@NonNull final PlayQueueItem... items) { - append(Arrays.asList(items)); + append(List.of(items)); } /** diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java index 527e80470..e51173214 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/SinglePlayQueue.java @@ -4,20 +4,19 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public final class SinglePlayQueue extends PlayQueue { public SinglePlayQueue(final StreamInfoItem item) { - super(0, Collections.singletonList(new PlayQueueItem(item))); + super(0, List.of(new PlayQueueItem(item))); } public SinglePlayQueue(final StreamInfo info) { - super(0, Collections.singletonList(new PlayQueueItem(info))); + super(0, List.of(new PlayQueueItem(info))); } public SinglePlayQueue(final StreamInfo info, final long startPosition) { - super(0, Collections.singletonList(new PlayQueueItem(info))); + super(0, List.of(new PlayQueueItem(info))); getItem().setRecoveryPosition(startPosition); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java index a445ea309..88b0b7735 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java @@ -3,8 +3,6 @@ package org.schabi.newpipe.settings.preferencesearch; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Stream; @@ -12,9 +10,9 @@ import java.util.stream.Stream; public class PreferenceSearchConfiguration { private PreferenceSearchFunction searcher = new PreferenceFuzzySearchFunction(); - private final List parserIgnoreElements = Collections.singletonList( - PreferenceCategory.class.getSimpleName()); - private final List parserContainerElements = Arrays.asList( + private final List parserIgnoreElements = List.of(PreferenceCategory.class + .getSimpleName()); + private final List parserContainerElements = List.of( PreferenceCategory.class.getSimpleName(), PreferenceScreen.class.getSimpleName()); diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java index 98d2a5d84..33856326c 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchItem.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.settings.preferencesearch; import androidx.annotation.NonNull; import androidx.annotation.XmlRes; -import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -92,11 +91,7 @@ public class PreferenceSearchItem { } public List getAllRelevantSearchFields() { - return Arrays.asList( - getTitle(), - getSummary(), - getEntries(), - getBreadcrumbs()); + return List.of(getTitle(), getSummary(), getEntries(), getBreadcrumbs()); } @NonNull diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java index 057ca50f0..40dacc964 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java @@ -10,8 +10,6 @@ import com.grack.nanojson.JsonStringWriter; import com.grack.nanojson.JsonWriter; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; /** @@ -20,11 +18,9 @@ import java.util.List; public final class TabsJsonHelper { private static final String JSON_TABS_ARRAY_KEY = "tabs"; - private static final List FALLBACK_INITIAL_TABS_LIST = Collections.unmodifiableList( - Arrays.asList( - Tab.Type.DEFAULT_KIOSK.getTab(), - Tab.Type.SUBSCRIPTIONS.getTab(), - Tab.Type.BOOKMARKS.getTab())); + private static final List FALLBACK_INITIAL_TABS_LIST = List.of( + Tab.Type.DEFAULT_KIOSK.getTab(), Tab.Type.SUBSCRIPTIONS.getTab(), + Tab.Type.BOOKMARKS.getTab()); private TabsJsonHelper() { } diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 9707956ed..1b3cb1651 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -31,14 +31,14 @@ import java.util.stream.Collectors; public final class ListHelper { // Video format in order of quality. 0=lowest quality, n=highest quality private static final List VIDEO_FORMAT_QUALITY_RANKING = - Arrays.asList(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4); + List.of(MediaFormat.v3GPP, MediaFormat.WEBM, MediaFormat.MPEG_4); // Audio format in order of quality. 0=lowest quality, n=highest quality private static final List AUDIO_FORMAT_QUALITY_RANKING = - Arrays.asList(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A); + List.of(MediaFormat.MP3, MediaFormat.WEBMA, MediaFormat.M4A); // Audio format in order of efficiency. 0=most efficient, n=least efficient private static final List AUDIO_FORMAT_EFFICIENCY_RANKING = - Arrays.asList(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); + List.of(MediaFormat.WEBMA, MediaFormat.M4A, MediaFormat.MP3); // Use a Set for better performance private static final Set HIGH_RESOLUTION_LIST = Set.of("1440p", "2160p"); diff --git a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java index 97e6e7563..34f99d262 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PeertubeHelper.java @@ -17,7 +17,6 @@ import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public final class PeertubeHelper { @@ -29,7 +28,7 @@ public final class PeertubeHelper { final String savedInstanceListKey = context.getString(R.string.peertube_instance_list_key); final String savedJson = sharedPreferences.getString(savedInstanceListKey, null); if (null == savedJson) { - return Collections.singletonList(getCurrentInstance()); + return List.of(getCurrentInstance()); } try { @@ -45,9 +44,8 @@ public final class PeertubeHelper { } return result; } catch (final JsonParserException e) { - return Collections.singletonList(getCurrentInstance()); + return List.of(getCurrentInstance()); } - } public static PeertubeInstance selectInstance(final PeertubeInstance instance, diff --git a/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java b/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java index f92863eab..a130359a3 100644 --- a/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java +++ b/app/src/test/java/org/schabi/newpipe/player/playqueue/PlayQueueTest.java @@ -153,7 +153,7 @@ public class PlayQueueTest { @Test public void itemsAreNotCloned() { final PlayQueueItem item = makeItemWithUrl("A url"); - final PlayQueue playQueue = makePlayQueue(0, Collections.singletonList(item)); + final PlayQueue playQueue = makePlayQueue(0, List.of(item)); // make sure that items are not cloned when added to the queue assertSame(playQueue.getItem(), item); diff --git a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java index c9d570c7d..4f9b0b497 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -6,7 +6,6 @@ import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.VideoStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; @@ -18,7 +17,7 @@ import androidx.annotation.Nullable; public class ListHelperTest { private static final String BEST_RESOLUTION_KEY = "best_resolution"; - private static final List AUDIO_STREAMS_TEST_LIST = Arrays.asList( + private static final List AUDIO_STREAMS_TEST_LIST = List.of( generateAudioStream("m4a-128-1", MediaFormat.M4A, 128), generateAudioStream("webma-192", MediaFormat.WEBMA, 192), generateAudioStream("mp3-64", MediaFormat.MP3, 64), @@ -30,7 +29,7 @@ public class ListHelperTest { generateAudioStream("mp3-192", MediaFormat.MP3, 192), generateAudioStream("webma-320", MediaFormat.WEBMA, 320)); - private static final List VIDEO_STREAMS_TEST_LIST = Arrays.asList( + private static final List VIDEO_STREAMS_TEST_LIST = List.of( generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false), generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false), @@ -38,7 +37,7 @@ public class ListHelperTest { generateVideoStream("mpeg_4-360", MediaFormat.MPEG_4, "360p", false), generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false)); - private static final List VIDEO_ONLY_STREAMS_TEST_LIST = Arrays.asList( + private static final List VIDEO_ONLY_STREAMS_TEST_LIST = List.of( generateVideoStream("mpeg_4-720-1", MediaFormat.MPEG_4, "720p", true), generateVideoStream("mpeg_4-720-2", MediaFormat.MPEG_4, "720p", true), generateVideoStream("mpeg_4-2160", MediaFormat.MPEG_4, "2160p", true), @@ -54,7 +53,7 @@ public class ListHelperTest { List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, true, false); - List expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60", + List expected = List.of("144p", "240p", "360p", "480p", "720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); assertEquals(expected.size(), result.size()); @@ -69,7 +68,7 @@ public class ListHelperTest { result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false, false); - expected = Arrays.asList("2160p60", "2160p", "1440p60", "1080p60", "1080p", "720p60", + expected = List.of("2160p60", "2160p", "1440p60", "1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -83,7 +82,7 @@ public class ListHelperTest { null, VIDEO_ONLY_STREAMS_TEST_LIST, true, true); List expected = - Arrays.asList("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); + List.of("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -97,7 +96,7 @@ public class ListHelperTest { result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, null, false, true); - expected = Arrays.asList("720p", "480p", "360p", "240p", "144p"); + expected = List.of("720p", "480p", "360p", "240p", "144p"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { assertEquals(expected.get(i), result.get(i).getResolution()); @@ -110,10 +109,10 @@ public class ListHelperTest { result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, true, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, true, true); - expected = Arrays.asList("144p", "240p", "360p", "480p", "720p", "720p60", + expected = List.of("144p", "240p", "360p", "480p", "720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); final List expectedVideoOnly = - Arrays.asList("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); + List.of("720p", "720p60", "1080p", "1080p60", "1440p60", "2160p", "2160p60"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -131,7 +130,7 @@ public class ListHelperTest { final List result = ListHelper.getSortedStreamVideosList(MediaFormat.MPEG_4, false, VIDEO_STREAMS_TEST_LIST, VIDEO_ONLY_STREAMS_TEST_LIST, false, false); - final List expected = Arrays.asList( + final List expected = List.of( "1080p60", "1080p", "720p60", "720p", "480p", "360p", "240p", "144p"); assertEquals(expected.size(), result.size()); for (int i = 0; i < result.size(); i++) { @@ -141,7 +140,7 @@ public class ListHelperTest { @Test public void getDefaultResolutionTest() { - final List testList = Arrays.asList( + final List testList = List.of( generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false), generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false), @@ -223,7 +222,7 @@ public class ListHelperTest { // Doesn't contain the preferred format // //////////////////////////////////////// - List testList = Arrays.asList( + List testList = List.of( generateAudioStream("m4a-128", MediaFormat.M4A, 128), generateAudioStream("webma-192", MediaFormat.WEBMA, 192)); // List doesn't contains this format @@ -237,7 +236,7 @@ public class ListHelperTest { // Multiple not-preferred-formats and equal bitrates // ////////////////////////////////////////////////////// - testList = new ArrayList<>(Arrays.asList( + testList = new ArrayList<>(List.of( generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192), generateAudioStream("m4a-192-1", MediaFormat.M4A, 192), generateAudioStream("webma-192-2", MediaFormat.WEBMA, 192), @@ -290,7 +289,7 @@ public class ListHelperTest { // Doesn't contain the preferred format // //////////////////////////////////////// - List testList = new ArrayList<>(Arrays.asList( + List testList = new ArrayList<>(List.of( generateAudioStream("m4a-128", MediaFormat.M4A, 128), generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192))); // List doesn't contains this format @@ -310,7 +309,7 @@ public class ListHelperTest { // Multiple not-preferred-formats and equal bitrates // ////////////////////////////////////////////////////// - testList = new ArrayList<>(Arrays.asList( + testList = new ArrayList<>(List.of( generateAudioStream("webma-192-1", MediaFormat.WEBMA, 192), generateAudioStream("m4a-192-1", MediaFormat.M4A, 192), generateAudioStream("webma-256", MediaFormat.WEBMA, 256), @@ -337,7 +336,7 @@ public class ListHelperTest { @Test public void getVideoDefaultStreamIndexCombinations() { - final List testList = Arrays.asList( + final List testList = List.of( generateVideoStream("mpeg_4-1080", MediaFormat.MPEG_4, "1080p", false), generateVideoStream("mpeg_4-720_60", MediaFormat.MPEG_4, "720p60", false), generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), From 55a995c4cd5990eb0a6b0058b58e7fa2e16a1c00 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 15 Jul 2022 08:13:08 +0530 Subject: [PATCH 228/992] Replace LinkedHashMap with List.of(). --- .../detail/VideoDetailPlayerCrasher.java | 74 +++++++------------ 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java index 55336a42f..c816723ff 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailPlayerCrasher.java @@ -6,6 +6,7 @@ import static com.google.android.exoplayer2.PlaybackException.ERROR_CODE_UNSPECI import android.content.Context; import android.util.Log; +import android.util.Pair; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -28,9 +29,7 @@ import org.schabi.newpipe.player.Player; import org.schabi.newpipe.util.ThemeHelper; import java.io.IOException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.List; import java.util.function.Supplier; /** @@ -43,50 +42,34 @@ public final class VideoDetailPlayerCrasher { // https://stackoverflow.com/a/54744028 private static final String TAG = "VideoDetPlayerCrasher"; - private static final Map> AVAILABLE_EXCEPTION_TYPES = - getExceptionTypes(); + private static final String DEFAULT_MSG = "Dummy"; + + private static final List>> + AVAILABLE_EXCEPTION_TYPES = List.of( + new Pair<>("Source", () -> ExoPlaybackException.createForSource( + new IOException(DEFAULT_MSG), + ERROR_CODE_BEHIND_LIVE_WINDOW + )), + new Pair<>("Renderer", () -> ExoPlaybackException.createForRenderer( + new Exception(DEFAULT_MSG), + "Dummy renderer", + 0, + null, + C.FORMAT_HANDLED, + /*isRecoverable=*/false, + ERROR_CODE_DECODING_FAILED + )), + new Pair<>("Unexpected", () -> ExoPlaybackException.createForUnexpected( + new RuntimeException(DEFAULT_MSG), + ERROR_CODE_UNSPECIFIED + )), + new Pair<>("Remote", () -> ExoPlaybackException.createForRemote(DEFAULT_MSG)) + ); private VideoDetailPlayerCrasher() { // No impls } - private static Map> getExceptionTypes() { - final String defaultMsg = "Dummy"; - final Map> exceptionTypes = new LinkedHashMap<>(); - exceptionTypes.put( - "Source", - () -> ExoPlaybackException.createForSource( - new IOException(defaultMsg), - ERROR_CODE_BEHIND_LIVE_WINDOW - ) - ); - exceptionTypes.put( - "Renderer", - () -> ExoPlaybackException.createForRenderer( - new Exception(defaultMsg), - "Dummy renderer", - 0, - null, - C.FORMAT_HANDLED, - /*isRecoverable=*/false, - ERROR_CODE_DECODING_FAILED - ) - ); - exceptionTypes.put( - "Unexpected", - () -> ExoPlaybackException.createForUnexpected( - new RuntimeException(defaultMsg), - ERROR_CODE_UNSPECIFIED - ) - ); - exceptionTypes.put( - "Remote", - () -> ExoPlaybackException.createForRemote(defaultMsg) - ); - - return Collections.unmodifiableMap(exceptionTypes); - } - private static Context getThemeWrapperContext(final Context context) { return new ContextThemeWrapper( context, @@ -121,10 +104,9 @@ public final class VideoDetailPlayerCrasher { .setNegativeButton(R.string.cancel, null) .create(); - for (final Map.Entry> entry - : AVAILABLE_EXCEPTION_TYPES.entrySet()) { + for (final Pair> entry : AVAILABLE_EXCEPTION_TYPES) { final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot(); - radioButton.setText(entry.getKey()); + radioButton.setText(entry.first); radioButton.setChecked(false); radioButton.setLayoutParams( new RadioGroup.LayoutParams( @@ -133,7 +115,7 @@ public final class VideoDetailPlayerCrasher { ) ); radioButton.setOnClickListener(v -> { - tryCrashPlayerWith(player, entry.getValue().get()); + tryCrashPlayerWith(player, entry.second.get()); alertDialog.cancel(); }); binding.list.addView(radioButton); From a6cc13845a70dd80aaa31efa291078c3534cca48 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Tue, 19 Jul 2022 08:52:16 +0530 Subject: [PATCH 229/992] Use Map.of(). --- .../helper/PlaybackParameterDialog.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 8a5a4f8d2..d542e1c53 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -30,7 +30,6 @@ import org.schabi.newpipe.player.ui.VideoPlayerUi; import org.schabi.newpipe.util.SimpleOnSeekBarChangeListener; import org.schabi.newpipe.util.SliderStrategy; -import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; @@ -334,10 +333,8 @@ public class PlaybackParameterDialog extends DialogFragment { } private Map getPitchControlModeComponentMappings() { - final Map mappings = new HashMap<>(); - mappings.put(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent); - mappings.put(PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone); - return mappings; + return Map.of(PITCH_CTRL_MODE_PERCENT, binding.pitchControlModePercent, + PITCH_CTRL_MODE_SEMITONE, binding.pitchControlModeSemitone); } private void changePitchControlMode(final boolean semitones) { @@ -407,13 +404,11 @@ public class PlaybackParameterDialog extends DialogFragment { } private Map getStepSizeComponentMappings() { - final Map mappings = new HashMap<>(); - mappings.put(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent); - mappings.put(STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent); - mappings.put(STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent); - mappings.put(STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent); - mappings.put(STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent); - return mappings; + return Map.of(STEP_1_PERCENT_VALUE, binding.stepSizeOnePercent, + STEP_5_PERCENT_VALUE, binding.stepSizeFivePercent, + STEP_10_PERCENT_VALUE, binding.stepSizeTenPercent, + STEP_25_PERCENT_VALUE, binding.stepSizeTwentyFivePercent, + STEP_100_PERCENT_VALUE, binding.stepSizeOneHundredPercent); } private void setStepSizeToUI(final double newStepSize) { From d62cdc659f2933a2f522e7054838f3d86162bde6 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 09:00:11 +0530 Subject: [PATCH 230/992] Use MathUtils.clamp(). Co-authored-by: Stypox --- .../org/schabi/newpipe/RouterActivity.java | 3 ++- .../org/schabi/newpipe/player/Player.java | 13 ++++--------- .../helper/PlaybackParameterDialog.java | 10 +++------- .../player/helper/PlayerSemitoneHelper.java | 4 +++- .../playqueue/PlayQueueItemTouchCallback.java | 15 +++++++++------ .../SeekbarPreviewThumbnailHelper.java | 19 ++++++------------- .../newpipe/player/ui/PopupPlayerUi.java | 16 +++++----------- .../newpipe/player/ui/VideoPlayerUi.java | 14 ++++---------- .../org/schabi/newpipe/util/Localization.java | 4 ++-- 9 files changed, 38 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index d055da1e8..9daceda08 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -30,6 +30,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.content.res.AppCompatResources; import androidx.core.app.NotificationCompat; import androidx.core.app.ServiceCompat; +import androidx.core.math.MathUtils; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; @@ -452,7 +453,7 @@ public class RouterActivity extends AppCompatActivity { } } - selectedRadioPosition = Math.min(Math.max(-1, selectedRadioPosition), choices.size() - 1); + selectedRadioPosition = MathUtils.clamp(selectedRadioPosition, -1, choices.size() - 1); if (selectedRadioPosition != -1) { ((RadioButton) radioGroup.getChildAt(selectedRadioPosition)).setChecked(true); } 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 43427ac27..159d361c1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -62,6 +62,7 @@ import android.view.LayoutInflater; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; import com.google.android.exoplayer2.C; @@ -591,7 +592,7 @@ public final class Player implements PlaybackListener, Listener { final long duration = simpleExoPlayer.getDuration(); // No checks due to https://github.com/TeamNewPipe/NewPipe/pull/7195#issuecomment-962624380 - setRecovery(queuePos, Math.max(0, Math.min(windowPos, duration))); + setRecovery(queuePos, MathUtils.clamp(windowPos, 0, duration)); } private void setRecovery(final int queuePos, final long windowPos) { @@ -1534,14 +1535,8 @@ public final class Player implements PlaybackListener, Listener { } if (!exoPlayerIsNull()) { // prevent invalid positions when fast-forwarding/-rewinding - long normalizedPositionMillis = positionMillis; - if (normalizedPositionMillis < 0) { - normalizedPositionMillis = 0; - } else if (normalizedPositionMillis > simpleExoPlayer.getDuration()) { - normalizedPositionMillis = simpleExoPlayer.getDuration(); - } - - simpleExoPlayer.seekTo(normalizedPositionMillis); + simpleExoPlayer.seekTo(MathUtils.clamp(positionMillis, 0, + simpleExoPlayer.getDuration())); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 8a5a4f8d2..83258bcd2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -21,6 +21,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; +import androidx.core.math.MathUtils; import androidx.fragment.app.DialogFragment; import androidx.preference.PreferenceManager; @@ -532,7 +533,7 @@ public class PlaybackParameterDialog extends DialogFragment { } private void setAndUpdateTempo(final double newTempo) { - this.tempo = calcValidTempo(newTempo); + this.tempo = MathUtils.clamp(newTempo, MIN_PITCH_OR_SPEED, MAX_PITCH_OR_SPEED); binding.tempoSeekbar.setProgress(QUADRATIC_STRATEGY.progressOf(tempo)); setText(binding.tempoCurrentText, PlayerHelper::formatSpeed, tempo); @@ -551,13 +552,8 @@ public class PlaybackParameterDialog extends DialogFragment { pitchPercent); } - private double calcValidTempo(final double newTempo) { - return Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newTempo)); - } - private double calcValidPitch(final double newPitch) { - final double calcPitch = - Math.max(MIN_PITCH_OR_SPEED, Math.min(MAX_PITCH_OR_SPEED, newPitch)); + final double calcPitch = MathUtils.clamp(newPitch, MIN_PITCH_OR_SPEED, MAX_PITCH_OR_SPEED); if (!isCurrentPitchControlModeSemitone()) { return calcPitch; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java index f3a71d7cd..f1ba90f8e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerSemitoneHelper.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.player.helper; +import androidx.core.math.MathUtils; + /** * Converts between percent and 12-tone equal temperament semitones. *
@@ -33,6 +35,6 @@ public final class PlayerSemitoneHelper { } private static int ensureSemitonesInRange(final int semitones) { - return Math.max(-SEMITONE_COUNT, Math.min(SEMITONE_COUNT, semitones)); + return MathUtils.clamp(semitones, -SEMITONE_COUNT, SEMITONE_COUNT); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java index b283e105e..de1359bca 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.player.playqueue; +import androidx.annotation.NonNull; +import androidx.core.math.MathUtils; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; @@ -16,18 +18,19 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC public abstract void onSwiped(int index); @Override - public int interpolateOutOfBoundsScroll(final RecyclerView recyclerView, final int viewSize, - final int viewSizeOutOfBounds, final int totalSize, - final long msSinceStartScroll) { + public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView, + final int viewSize, final int viewSizeOutOfBounds, + final int totalSize, final long msSinceStartScroll) { final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); - final int clampedAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY, - Math.min(Math.abs(standardSpeed), MAXIMUM_INITIAL_DRAG_VELOCITY)); + final int clampedAbsVelocity = MathUtils.clamp(Math.abs(standardSpeed), + MINIMUM_INITIAL_DRAG_VELOCITY, MAXIMUM_INITIAL_DRAG_VELOCITY); return clampedAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); } @Override - public boolean onMove(final RecyclerView recyclerView, final RecyclerView.ViewHolder source, + public boolean onMove(@NonNull final RecyclerView recyclerView, + final RecyclerView.ViewHolder source, final RecyclerView.ViewHolder target) { if (source.getItemViewType() != target.getItemViewType()) { return false; diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java index 54d11da83..dba28a69a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java @@ -8,13 +8,13 @@ import android.widget.ImageView; import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.util.DeviceUtils; import java.lang.annotation.Retention; -import java.util.Objects; import java.util.Optional; import java.util.function.IntSupplier; @@ -79,19 +79,12 @@ public final class SeekbarPreviewThumbnailHelper { // Resize original bitmap try { - Objects.requireNonNull(srcBitmap); - final int srcWidth = srcBitmap.getWidth() > 0 ? srcBitmap.getWidth() : 1; - final int newWidth = Math.max( - Math.min( - // Use 1/4 of the width for the preview - Math.round(baseViewWidthSupplier.getAsInt() / 4f), - // Scaling more than that factor looks really pixelated -> max - Math.round(srcWidth * 2.5f) - ), - // Min width = 10dp - DeviceUtils.dpToPx(10, context) - ); + // Use 1/4 of the width for the preview + final int newWidth = MathUtils.clamp(Math.round(baseViewWidthSupplier.getAsInt() / 4f), + DeviceUtils.dpToPx(10, context), + // Scaling more than that factor looks really pixelated -> max + Math.round(srcWidth * 2.5f)); final float scaleFactor = (float) newWidth / srcWidth; final int newHeight = (int) (srcBitmap.getHeight() * scaleFactor); 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 bb810f86b..74adf281c 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 @@ -27,6 +27,7 @@ import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; +import androidx.core.math.MathUtils; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.SubtitleView; @@ -247,17 +248,10 @@ public final class PopupPlayerUi extends VideoPlayerUi { return; } - if (popupLayoutParams.x < 0) { - popupLayoutParams.x = 0; - } else if (popupLayoutParams.x > screenWidth - popupLayoutParams.width) { - popupLayoutParams.x = screenWidth - popupLayoutParams.width; - } - - if (popupLayoutParams.y < 0) { - popupLayoutParams.y = 0; - } else if (popupLayoutParams.y > screenHeight - popupLayoutParams.height) { - popupLayoutParams.y = screenHeight - popupLayoutParams.height; - } + popupLayoutParams.x = MathUtils.clamp(popupLayoutParams.x, 0, screenWidth + - popupLayoutParams.width); + popupLayoutParams.y = MathUtils.clamp(popupLayoutParams.y, 0, screenHeight + - popupLayoutParams.height); } public void updateScreenSize() { diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java index 217dd38b3..a972d2f71 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/VideoPlayerUi.java @@ -43,6 +43,7 @@ import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.view.ContextThemeWrapper; import androidx.appcompat.widget.PopupMenu; import androidx.core.graphics.Insets; +import androidx.core.math.MathUtils; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; @@ -580,16 +581,9 @@ public abstract class VideoPlayerUi extends PlayerUi currentSeekbarLeft - (binding.seekbarPreviewContainer.getWidth() / 2); // Fix the position so it's within the boundaries - final int checkedContainerLeft = - Math.max( - Math.min( - uncheckedContainerLeft, - // Max left - binding.playbackWindowRoot.getWidth() - - binding.seekbarPreviewContainer.getWidth() - ), - 0 // Min left - ); + final int checkedContainerLeft = MathUtils.clamp(uncheckedContainerLeft, + 0, binding.playbackWindowRoot.getWidth() + - binding.seekbarPreviewContainer.getWidth()); // See also: https://stackoverflow.com/a/23249734 final LinearLayout.LayoutParams params = 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 b222f6abf..5ac24503d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -13,6 +13,7 @@ import android.util.DisplayMetrics; import androidx.annotation.NonNull; import androidx.annotation.PluralsRes; import androidx.annotation.StringRes; +import androidx.core.math.MathUtils; import androidx.preference.PreferenceManager; import org.ocpsoft.prettytime.PrettyTime; @@ -247,8 +248,7 @@ public final class Localization { // is not the responsibility of this method handle long numbers // (it probably will fall in the "other" category, // or some language have some specific rule... then we have to change it) - final int safeCount = count > Integer.MAX_VALUE ? Integer.MAX_VALUE - : count < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) count; + final int safeCount = (int) MathUtils.clamp(count, Integer.MIN_VALUE, Integer.MAX_VALUE); return context.getResources().getQuantityString(pluralId, safeCount, formattedCount); } From 394eb92e714e4c2f7375aea4bf22afd5a84ba660 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 09:09:18 +0530 Subject: [PATCH 231/992] Use coerceIn(). --- .../gesture/PopupPlayerGestureListener.kt | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt index bda6ee8d1..01b15f30a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -233,22 +233,14 @@ class PopupPlayerGestureListener( isMoving = true - val diffX: Float = (movingEvent.rawX - initialEvent.rawX) - var posX: Float = (initialPopupX + diffX) - val diffY: Float = (movingEvent.rawY - initialEvent.rawY) - var posY: Float = (initialPopupY + diffY) - - if (posX > playerUi.screenWidth - playerUi.popupLayoutParams.width) { - posX = (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() - } else if (posX < 0) { - posX = 0f - } - - if (posY > playerUi.screenHeight - playerUi.popupLayoutParams.height) { - posY = (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() - } else if (posY < 0) { - posY = 0f - } + val diffX = (movingEvent.rawX - initialEvent.rawX) + val posX = (initialPopupX + diffX).coerceIn( + 0f, (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() + ) + val diffY = (movingEvent.rawY - initialEvent.rawY) + val posY = (initialPopupY + diffY).coerceIn( + 0f, (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() + ) playerUi.popupLayoutParams.x = posX.toInt() playerUi.popupLayoutParams.y = posY.toInt() From c5b970cca3e6ba2f8071e9483167697c591925e1 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 20 Jul 2022 14:50:23 +0200 Subject: [PATCH 232/992] Improve code style in List.of() --- .../preferencesearch/PreferenceSearchConfiguration.java | 4 ++-- .../java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java index 88b0b7735..1ded181c8 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchConfiguration.java @@ -10,8 +10,8 @@ import java.util.stream.Stream; public class PreferenceSearchConfiguration { private PreferenceSearchFunction searcher = new PreferenceFuzzySearchFunction(); - private final List parserIgnoreElements = List.of(PreferenceCategory.class - .getSimpleName()); + private final List parserIgnoreElements = List.of( + PreferenceCategory.class.getSimpleName()); private final List parserContainerElements = List.of( PreferenceCategory.class.getSimpleName(), PreferenceScreen.class.getSimpleName()); diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java index 40dacc964..32f25ccbd 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/TabsJsonHelper.java @@ -19,7 +19,8 @@ public final class TabsJsonHelper { private static final String JSON_TABS_ARRAY_KEY = "tabs"; private static final List FALLBACK_INITIAL_TABS_LIST = List.of( - Tab.Type.DEFAULT_KIOSK.getTab(), Tab.Type.SUBSCRIPTIONS.getTab(), + Tab.Type.DEFAULT_KIOSK.getTab(), + Tab.Type.SUBSCRIPTIONS.getTab(), Tab.Type.BOOKMARKS.getTab()); private TabsJsonHelper() { } From 373ee53143fd4de2cfb6cd5cbf2ce8f27f04ac99 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 20 Jul 2022 15:05:25 +0200 Subject: [PATCH 233/992] Improve code style --- .../player/playqueue/PlayQueueItemTouchCallback.java | 6 ++++-- .../seekbarpreview/SeekbarPreviewThumbnailHelper.java | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java index de1359bca..6e2792d4f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemTouchCallback.java @@ -19,8 +19,10 @@ public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleC @Override public int interpolateOutOfBoundsScroll(@NonNull final RecyclerView recyclerView, - final int viewSize, final int viewSizeOutOfBounds, - final int totalSize, final long msSinceStartScroll) { + final int viewSize, + final int viewSizeOutOfBounds, + final int totalSize, + final long msSinceStartScroll) { final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); final int clampedAbsVelocity = MathUtils.clamp(Math.abs(standardSpeed), diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java index dba28a69a..43d89055c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHelper.java @@ -80,10 +80,12 @@ public final class SeekbarPreviewThumbnailHelper { // Resize original bitmap try { final int srcWidth = srcBitmap.getWidth() > 0 ? srcBitmap.getWidth() : 1; - // Use 1/4 of the width for the preview - final int newWidth = MathUtils.clamp(Math.round(baseViewWidthSupplier.getAsInt() / 4f), + final int newWidth = MathUtils.clamp( + // Use 1/4 of the width for the preview + Math.round(baseViewWidthSupplier.getAsInt() / 4f), + // But have a min width of 10dp DeviceUtils.dpToPx(10, context), - // Scaling more than that factor looks really pixelated -> max + // And scaling more than that factor looks really pixelated -> max Math.round(srcWidth * 2.5f)); final float scaleFactor = (float) newWidth / srcWidth; From b845645b80c07b77b97fc464fdc451c8eb289be2 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 06:21:51 +0530 Subject: [PATCH 234/992] Use IO extensions. Co-authored-by: Stypox --- .../newpipe/about/LicenseFragmentHelper.kt | 32 +++---------------- .../settings/ContentSettingsManager.kt | 9 ++---- .../schabi/newpipe/util/ReleaseVersionUtil.kt | 6 +--- 3 files changed, 8 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index c1dd38389..3acb50cb9 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -12,10 +12,7 @@ import org.schabi.newpipe.R import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.external_communication.ShareUtils -import java.io.BufferedReader import java.io.IOException -import java.io.InputStreamReader -import java.nio.charset.StandardCharsets object LicenseFragmentHelper { /** @@ -25,32 +22,13 @@ object LicenseFragmentHelper { * styled according to the context's theme */ private fun getFormattedLicense(context: Context, license: License): String { - val licenseContent = StringBuilder() - val webViewData: String try { - BufferedReader( - InputStreamReader( - context.assets.open(license.filename), - StandardCharsets.UTF_8 - ) - ).use { `in` -> - var str: String? - while (`in`.readLine().also { str = it } != null) { - licenseContent.append(str) - } - + return context.assets.open(license.filename).bufferedReader().use { it.readText() } // split the HTML file and insert the stylesheet into the HEAD of the file - webViewData = "$licenseContent".replace( - "", - "" - ) - } + .replace("", "") } catch (e: IOException) { - throw IllegalArgumentException( - "Could not get license file: " + license.filename, e - ) + throw IllegalArgumentException("Could not get license file: ${license.filename}", e) } - return webViewData } /** @@ -118,9 +96,7 @@ object LicenseFragmentHelper { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { formattedLicense -> - val webViewData = Base64.encodeToString( - formattedLicense.toByteArray(StandardCharsets.UTF_8), Base64.NO_PADDING - ) + val webViewData = Base64.encodeToString(formattedLicense.toByteArray(), Base64.NO_PADDING) val webView = WebView(context) webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt index 3ac275695..8adf6a649 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsManager.kt @@ -5,9 +5,6 @@ import android.util.Log import org.schabi.newpipe.streams.io.SharpOutputStream import org.schabi.newpipe.streams.io.StoredFileHelper import org.schabi.newpipe.util.ZipHelper -import java.io.BufferedOutputStream -import java.io.FileInputStream -import java.io.FileOutputStream import java.io.IOException import java.io.ObjectInputStream import java.io.ObjectOutputStream @@ -25,12 +22,12 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) { @Throws(Exception::class) fun exportDatabase(preferences: SharedPreferences, file: StoredFileHelper) { file.create() - ZipOutputStream(BufferedOutputStream(SharpOutputStream(file.stream))) + ZipOutputStream(SharpOutputStream(file.stream).buffered()) .use { outZip -> ZipHelper.addFileToZip(outZip, fileLocator.db.path, "newpipe.db") try { - ObjectOutputStream(FileOutputStream(fileLocator.settings)).use { output -> + ObjectOutputStream(fileLocator.settings.outputStream()).use { output -> output.writeObject(preferences.all) output.flush() } @@ -74,7 +71,7 @@ class ContentSettingsManager(private val fileLocator: NewPipeFileLocator) { try { val preferenceEditor = preferences.edit() - ObjectInputStream(FileInputStream(fileLocator.settings)).use { input -> + ObjectInputStream(fileLocator.settings.inputStream()).use { input -> preferenceEditor.clear() @Suppress("UNCHECKED_CAST") val entries = input.readObject() as Map diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt index 21a9059e2..0c66cc6d4 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt @@ -7,8 +7,6 @@ import org.schabi.newpipe.App import org.schabi.newpipe.error.ErrorInfo import org.schabi.newpipe.error.ErrorUtil.Companion.createNotification import org.schabi.newpipe.error.UserAction -import java.io.ByteArrayInputStream -import java.io.InputStream import java.security.MessageDigest import java.security.NoSuchAlgorithmException import java.security.cert.CertificateEncodingException @@ -47,10 +45,8 @@ object ReleaseVersionUtil { return "" } val x509cert = try { - val cert = signatures[0].toByteArray() - val input: InputStream = ByteArrayInputStream(cert) val cf = CertificateFactory.getInstance("X509") - cf.generateCertificate(input) as X509Certificate + cf.generateCertificate(signatures[0].toByteArray().inputStream()) as X509Certificate } catch (e: CertificateException) { showRequestError(app, e, "Certificate error") return "" From 8b400b48f7ad5ecfa7762854b55a06aa5c828dd0 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 20 Jul 2022 04:36:54 +0530 Subject: [PATCH 235/992] Refactor notifying method in PlayQueue. --- .../newpipe/player/playqueue/AbstractInfoPlayQueue.java | 4 ++-- .../org/schabi/newpipe/player/playqueue/PlayQueue.java | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) 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 df2747c3b..e51ee4720 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 @@ -82,7 +82,7 @@ abstract class AbstractInfoPlayQueue> public void onError(@NonNull final Throwable e) { Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); isComplete = true; - append(); // Notify change + notifyChange(); } }; } @@ -117,7 +117,7 @@ abstract class AbstractInfoPlayQueue> public void onError(@NonNull final Throwable e) { Log.e(getTag(), "Error fetching more playlist, marking playlist as complete.", e); isComplete = true; - append(); // Notify change + notifyChange(); } }; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java index d54fed248..edf5a771c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueue.java @@ -257,13 +257,10 @@ public abstract class PlayQueue implements Serializable { } /** - * Appends the given {@link PlayQueueItem}s to the current play queue. - * - * @see #append(List items) - * @param items {@link PlayQueueItem}s to append + * Notifies that a change has occurred. */ - public synchronized void append(@NonNull final PlayQueueItem... items) { - append(List.of(items)); + public synchronized void notifyChange() { + broadcast(new AppendEvent(0)); } /** From 6d1c61407d45e54b810fec7ce187afbbfc224b41 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 20 Jul 2022 07:14:40 +0530 Subject: [PATCH 236/992] Remove unnecessary method in ChannelFragment. --- .../newpipe/fragments/list/channel/ChannelFragment.java | 6 +----- 1 file changed, 1 insertion(+), 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 e44048473..8ed9389c3 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 @@ -578,17 +578,13 @@ public class ChannelFragment extends BaseListInfoFragment streamItems = infoListAdapter.getItemsList().stream() .filter(StreamInfoItem.class::isInstance) .map(StreamInfoItem.class::cast) .collect(Collectors.toList()); return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(), - currentInfo.getNextPage(), streamItems, index); + currentInfo.getNextPage(), streamItems, 0); } /*////////////////////////////////////////////////////////////////////////// From f1dab11f1fecdca4c8f3fd3c393718e6528d81e3 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Thu, 21 Jul 2022 07:19:23 +0530 Subject: [PATCH 237/992] Remove deprecated method calls in FocusAwareCoordinator. --- .../newpipe/views/FocusAwareCoordinator.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java index 747aed025..d4fafc31a 100644 --- a/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java +++ b/app/src/main/java/org/schabi/newpipe/views/FocusAwareCoordinator.java @@ -27,6 +27,7 @@ import android.view.WindowInsets; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.WindowInsetsCompat; import org.schabi.newpipe.R; @@ -83,23 +84,22 @@ public final class FocusAwareCoordinator extends CoordinatorLayout { } } - if (consumed) { - insets.consumeSystemWindowInsets(); - } - return insets; + return consumed ? WindowInsetsCompat.CONSUMED.toWindowInsets() : insets; } /** - * Adjusts player's controls manually because fitsSystemWindows doesn't work when multiple + * Adjusts player's controls manually because onApplyWindowInsets doesn't work when multiple * receivers adjust its bounds. So when two listeners are present (like in profile page) * the player's controls will not receive insets. This method fixes it */ @Override - protected boolean fitSystemWindows(final Rect insets) { + public WindowInsets onApplyWindowInsets(final WindowInsets windowInsets) { + final var windowInsetsCompat = WindowInsetsCompat.toWindowInsetsCompat(windowInsets, this); + final var insets = windowInsetsCompat.getInsets(WindowInsetsCompat.Type.systemBars()); final ViewGroup controls = findViewById(R.id.playbackControlRoot); if (controls != null) { controls.setPadding(insets.left, insets.top, insets.right, insets.bottom); } - return super.fitSystemWindows(insets); + return super.onApplyWindowInsets(windowInsets); } } From 37275e8fe3b07b3ac7360a0e2e85a54188f78304 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 15:10:35 +0200 Subject: [PATCH 238/992] Fix wrong thumbnail used as placeholder for channel --- .../newpipe/info_list/holder/ChannelMiniInfoItemHolder.java | 2 +- .../org/schabi/newpipe/local/subscription/item/ChannelItem.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index aa4f4c9f0..89398a1e5 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -42,7 +42,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { itemTitleView.setText(item.getName()); itemAdditionalDetailView.setText(getDetailLine(item)); - PicassoHelper.loadThumbnail(item.getThumbnailUrl()).into(itemThumbnailView); + PicassoHelper.loadAvatar(item.getThumbnailUrl()).into(itemThumbnailView); itemView.setOnClickListener(view -> { if (itemBuilder.getOnChannelSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt index a8c05838f..bee2e910a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/item/ChannelItem.kt @@ -39,7 +39,7 @@ class ChannelItem( itemChannelDescriptionView.text = infoItem.description } - PicassoHelper.loadThumbnail(infoItem.thumbnailUrl).into(itemThumbnailView) + PicassoHelper.loadAvatar(infoItem.thumbnailUrl).into(itemThumbnailView) gesturesListener?.run { viewHolder.root.setOnClickListener { selected(infoItem) } From f4fe5fcb165d9545b95e050652d54b6e3cc9453d Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 16:04:41 +0200 Subject: [PATCH 239/992] Fix ListHelperTest failure caused by immutable list being used --- .../main/java/org/schabi/newpipe/util/ListHelper.java | 9 +++++---- .../java/org/schabi/newpipe/util/ListHelperTest.java | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 1b3cb1651..fbbe43513 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -230,14 +230,15 @@ public final class ListHelper { } /** - * Return the index of the default stream in the list, based on the parameters - * defaultResolution and defaultFormat. + * Return the index of the default stream in the list, that will be sorted in the process, based + * on the parameters defaultResolution and defaultFormat. * * @param defaultResolution the default resolution to look for * @param bestResolutionKey key of the best resolution * @param defaultFormat the default format to look for - * @param videoStreams list of the video streams to check - * @return index of the default resolution&format + * @param videoStreams a mutable list of the video streams to check (it will be sorted in + * place) + * @return index of the default resolution&format in the sorted videoStreams */ static int getDefaultResolutionIndex(final String defaultResolution, final String bestResolutionKey, diff --git a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java index 4f9b0b497..8a75b1b4e 100644 --- a/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/util/ListHelperTest.java @@ -140,7 +140,7 @@ public class ListHelperTest { @Test public void getDefaultResolutionTest() { - final List testList = List.of( + final List testList = new ArrayList<>(List.of( generateVideoStream("mpeg_4-720", MediaFormat.MPEG_4, "720p", false), generateVideoStream("v3gpp-240", MediaFormat.v3GPP, "240p", false), generateVideoStream("webm-480", MediaFormat.WEBM, "480p", false), @@ -148,7 +148,7 @@ public class ListHelperTest { generateVideoStream("mpeg_4-240", MediaFormat.MPEG_4, "240p", false), generateVideoStream("webm-144", MediaFormat.WEBM, "144p", false), generateVideoStream("mpeg_4-360", MediaFormat.MPEG_4, "360p", false), - generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false)); + generateVideoStream("webm-360", MediaFormat.WEBM, "360p", false))); VideoStream result = testList.get(ListHelper.getDefaultResolutionIndex( "720p", BEST_RESOLUTION_KEY, MediaFormat.MPEG_4, testList)); assertEquals("720p", result.getResolution()); From 51e72d1a0544ed47789cd102e46a92ec82eaea29 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Sun, 24 Jul 2022 14:57:23 +0200 Subject: [PATCH 240/992] Removed the "(beta)"-tag from services (#8637) --- app/src/main/java/org/schabi/newpipe/MainActivity.java | 9 ++++----- .../main/java/org/schabi/newpipe/util/ServiceHelper.java | 9 --------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 4a982874c..d4b2305c7 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -374,8 +374,7 @@ public class MainActivity extends AppCompatActivity { private void showServices() { for (final StreamingService s : NewPipe.getServices()) { - final String title = s.getServiceInfo().getName() - + (ServiceHelper.isBeta(s) ? " (beta)" : ""); + final String title = s.getServiceInfo().getName(); final MenuItem menuItem = drawerLayoutBinding.navigation.getMenu() .add(R.id.menu_services_group, s.getServiceId(), ORDER, title) @@ -383,7 +382,7 @@ public class MainActivity extends AppCompatActivity { // peertube specifics if (s.getServiceId() == 3) { - enhancePeertubeMenu(s, menuItem); + enhancePeertubeMenu(menuItem); } } drawerLayoutBinding.navigation.getMenu() @@ -391,9 +390,9 @@ public class MainActivity extends AppCompatActivity { .setChecked(true); } - private void enhancePeertubeMenu(final StreamingService s, final MenuItem menuItem) { + private void enhancePeertubeMenu(final MenuItem menuItem) { final PeertubeInstance currentInstance = PeertubeHelper.getCurrentInstance(); - menuItem.setTitle(currentInstance.getName() + (ServiceHelper.isBeta(s) ? " (beta)" : "")); + menuItem.setTitle(currentInstance.getName()); final Spinner spinner = InstanceSpinnerLayoutBinding.inflate(LayoutInflater.from(this)) .getRoot(); final List instances = PeertubeHelper.getInstanceList(this); diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index b13ae4a97..acd019ba0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -169,15 +169,6 @@ public final class ServiceHelper { } } - public static boolean isBeta(final StreamingService s) { - switch (s.getServiceInfo().getName()) { - case "YouTube": - return false; - default: - return true; - } - } - public static void initService(final Context context, final int serviceId) { if (serviceId == ServiceList.PeerTube.getServiceId()) { final SharedPreferences sharedPreferences = PreferenceManager From 8f5d564f8430ec6d5e582a2d1e9267d84d4c0f4a Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 20 Jul 2022 21:01:17 -0400 Subject: [PATCH 241/992] Migrate NoNonsense-FilePicker to our updated fork --- app/build.gradle | 4 +--- .../org/schabi/newpipe/util/FilePickerActivityHelper.java | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..dfa512d22 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -188,6 +188,7 @@ dependencies { // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' implementation 'com.github.TeamNewPipe:NewPipeExtractor:5219a705bab539cf8c6624d0cec216e76e85f0b1' + implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" @@ -257,9 +258,6 @@ dependencies { implementation "io.noties.markwon:core:${markwonVersion}" implementation "io.noties.markwon:linkify:${markwonVersion}" - // File picker - implementation "com.nononsenseapps:filepicker:4.2.1" - // Crash reporting implementation "ch.acra:acra-core:5.9.3" diff --git a/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java b/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java index 20d8ce30c..d7fb39651 100644 --- a/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java @@ -76,7 +76,7 @@ public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.File public static class CustomFilePickerFragment extends FilePickerFragment { @Override - public View onCreateView(final LayoutInflater inflater, final ViewGroup container, + public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { return super.onCreateView(inflater, container, savedInstanceState); } @@ -138,7 +138,7 @@ public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.File } @Override - public void onLoadFinished(final Loader> loader, + public void onLoadFinished(@NonNull final Loader> loader, final SortedList data) { super.onLoadFinished(loader, data); layoutManager.scrollToPosition(0); From baabba1deade7e798b7852871039ed148ce6b1f6 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Wed, 20 Jul 2022 21:32:27 -0400 Subject: [PATCH 242/992] Disable Jetifier --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 76b51ef0b..032d70cee 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -android.enableJetifier=true +android.enableJetifier=false android.useAndroidX=true org.gradle.jvmargs=-Xmx2048M systemProp.file.encoding=utf-8 From 229422bfa95c42f436d4bd21a947c0e5642b64ea Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 24 Jul 2022 14:11:31 -0400 Subject: [PATCH 243/992] Update ExoPlayer to 2.18.1 --- app/build.gradle | 2 +- .../schabi/newpipe/player/datasource/YoutubeHttpDataSource.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..64378378e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -102,7 +102,7 @@ ext { androidxWorkVersion = '2.7.1' icepickVersion = '3.2.0' - exoPlayerVersion = '2.18.0' + exoPlayerVersion = '2.18.1' googleAutoServiceVersion = '1.0.1' groupieVersion = '2.10.1' markwonVersion = '4.6.2' diff --git a/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java b/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java index 2eecddf36..cf1f03b45 100644 --- a/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/datasource/YoutubeHttpDataSource.java @@ -1,5 +1,5 @@ /* - * Based on ExoPlayer's DefaultHttpDataSource, version 2.18.0. + * Based on ExoPlayer's DefaultHttpDataSource, version 2.18.1. * * Original source code copyright (C) 2016 The Android Open Source Project, licensed under the * Apache License, Version 2.0. From d7a654fc27d72334a01980e1b527007053734b48 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 24 Jul 2022 15:35:33 -0400 Subject: [PATCH 244/992] Update AndroidX Fragment to 1.4.1 --- app/build.gradle | 2 +- .../schabi/newpipe/player/helper/PlaybackParameterDialog.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..7aa0fec28 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -202,7 +202,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.documentfile:documentfile:1.0.1' - implementation 'androidx.fragment:fragment-ktx:1.3.6' + implementation 'androidx.fragment:fragment-ktx:1.4.1' implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index a9b507bd4..796208a04 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -11,7 +11,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.widget.CheckBox; import android.widget.SeekBar; @@ -149,7 +148,7 @@ public class PlaybackParameterDialog extends DialogFragment { assureCorrectAppLanguage(getContext()); Icepick.restoreInstanceState(this, savedInstanceState); - binding = DialogPlaybackParameterBinding.inflate(LayoutInflater.from(getContext())); + binding = DialogPlaybackParameterBinding.inflate(getLayoutInflater()); initUI(); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) From d66997c2ed3afa66b5fe7432dfad6dfc3ab92383 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 24 Jul 2022 16:51:26 -0400 Subject: [PATCH 245/992] Update Google Material to 1.6.1 --- app/build.gradle | 2 +- .../newpipe/player/gesture/CustomBottomSheetBehavior.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..b4f17e41f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -218,7 +218,7 @@ dependencies { implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation "androidx.work:work-runtime-ktx:${androidxWorkVersion}" implementation "androidx.work:work-rxjava3:${androidxWorkVersion}" - implementation 'com.google.android.material:material:1.5.0' + implementation 'com.google.android.material:material:1.6.1' /** Third-party libraries **/ // Instance state boilerplate elimination diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java index 41046784f..0970dbeb6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/CustomBottomSheetBehavior.java @@ -8,6 +8,7 @@ import android.view.View; import android.widget.FrameLayout; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; import com.google.android.material.bottomsheet.BottomSheetBehavior; @@ -18,7 +19,8 @@ import java.util.List; public class CustomBottomSheetBehavior extends BottomSheetBehavior { - public CustomBottomSheetBehavior(final Context context, final AttributeSet attrs) { + public CustomBottomSheetBehavior(@NonNull final Context context, + @Nullable final AttributeSet attrs) { super(context, attrs); } @@ -32,7 +34,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior @Override public boolean onInterceptTouchEvent(@NonNull final CoordinatorLayout parent, @NonNull final FrameLayout child, - final MotionEvent event) { + @NonNull final MotionEvent event) { // Drop following when action ends if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) { From 81fb44c45c8acf6d235e8afff31f348bccf8e2b7 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Thu, 21 Jul 2022 06:56:51 +0530 Subject: [PATCH 246/992] Remove uses of setBottomSheetCallback(). --- .../fragments/detail/VideoDetailFragment.java | 18 ++++++++++++------ 1 file changed, 12 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 f4838482b..4ad270c50 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 @@ -208,6 +208,8 @@ public final class VideoDetailFragment private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); + private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback; + /*////////////////////////////////////////////////////////////////////////// // Service management //////////////////////////////////////////////////////////////////////////*/ @@ -386,7 +388,7 @@ public final class VideoDetailFragment disposables.clear(); positionSubscriber = null; currentWorker = null; - bottomSheetBehavior.setBottomSheetCallback(null); + bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallback); if (activity.isFinishing()) { playQueue = null; @@ -2289,7 +2291,7 @@ public final class VideoDetailFragment } } - bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull final View bottomSheet, final int newState) { bottomSheetState = newState; @@ -2343,12 +2345,14 @@ public final class VideoDetailFragment } if (isPlayerAvailable()) { player.UIs().get(MainPlayerUi.class).ifPresent(ui -> { - if (ui.isControlsVisible()) { - ui.hideControls(0, 0); - } + if (ui.isControlsVisible()) { + ui.hideControls(0, 0); + } }); } break; + case BottomSheetBehavior.STATE_HALF_EXPANDED: + break; } } @@ -2356,7 +2360,9 @@ public final class VideoDetailFragment public void onSlide(@NonNull final View bottomSheet, final float slideOffset) { setOverlayLook(binding.appBarLayout, behavior, slideOffset); } - }); + }; + + bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback); // User opened a new page and the player will hide itself activity.getSupportFragmentManager().addOnBackStackChangedListener(() -> { From 81c4b822e0e1af82e1ec031457b57e80611ae003 Mon Sep 17 00:00:00 2001 From: Mohammed Anas Date: Tue, 26 Jul 2022 23:29:43 +0300 Subject: [PATCH 247/992] Add "needs triage" label to issue templates (#8643) This label would make it easier for issue triagers to know what they haven't triaged yet. --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/question.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a0a9f9ef5..3abb1fbb1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug report description: Create a bug report to help us improve -labels: [bug] +labels: [bug, needs triage] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 83d6f0299..9fc3c1632 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,6 @@ name: Feature request description: Suggest an idea for this project -labels: [enhancement] +labels: [enhancement, needs triage] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 4c42ab26a..8cf22d8af 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -1,6 +1,6 @@ name: Question description: Ask about anything NewPipe-related -labels: [question] +labels: [question, needs triage] body: - type: markdown attributes: From af9c2bd59db4da18b707c2ae178d5852c168a5a5 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 27 Jul 2022 07:54:49 +0530 Subject: [PATCH 248/992] Use stackTraceToString(). --- .../java/org/schabi/newpipe/error/ErrorInfo.kt | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt index f9f9f003a..d87fa3330 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorInfo.kt @@ -14,8 +14,6 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor.DeobfuscateException import org.schabi.newpipe.ktx.isNetworkRelated import org.schabi.newpipe.util.ServiceHelper -import java.io.PrintWriter -import java.io.StringWriter @Parcelize class ErrorInfo( @@ -80,19 +78,10 @@ class ErrorInfo( companion object { const val SERVICE_NONE = "none" - private fun getStackTrace(throwable: Throwable): String { - StringWriter().use { stringWriter -> - PrintWriter(stringWriter, true).use { printWriter -> - throwable.printStackTrace(printWriter) - return stringWriter.buffer.toString() - } - } - } + fun throwableToStringList(throwable: Throwable) = arrayOf(throwable.stackTraceToString()) - fun throwableToStringList(throwable: Throwable) = arrayOf(getStackTrace(throwable)) - - fun throwableListToStringList(throwable: List) = - Array(throwable.size) { i -> getStackTrace(throwable[i]) } + fun throwableListToStringList(throwableList: List) = + throwableList.map { it.stackTraceToString() }.toTypedArray() private fun getInfoServiceName(info: Info?) = if (info == null) SERVICE_NONE else ServiceHelper.getNameOfServiceById(info.serviceId) From 69942003f7dcb0adc0564dd1bc7e47fcd9a11965 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 29 Jul 2022 09:21:02 +0530 Subject: [PATCH 249/992] Sort tags case-insensitively. --- .../schabi/newpipe/fragments/detail/DescriptionFragment.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java index bbe52f2e9..bf7f8fa5d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -202,10 +202,9 @@ public class DescriptionFragment extends BaseFragment { private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { if (streamInfo.getTags() != null && !streamInfo.getTags().isEmpty()) { - final ItemMetadataTagsBinding itemBinding = - ItemMetadataTagsBinding.inflate(inflater, layout, false); + final var itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - streamInfo.getTags().stream().sorted().forEach(tag -> { + streamInfo.getTags().stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(tag -> { final Chip chip = (Chip) inflater.inflate(R.layout.chip, itemBinding.metadataTagsChips, false); chip.setText(tag); From 630558ed4f55e77bed1509c05d0b4a1ae22c3d45 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 30 Jul 2022 01:39:02 +0530 Subject: [PATCH 250/992] Use nested functions. --- .../main/java/org/schabi/newpipe/ktx/View.kt | 49 ++++++------------- 1 file changed, 14 insertions(+), 35 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 56c9d825a..5ab8dbdc2 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -90,64 +90,43 @@ fun View.animate( */ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) { if (MainActivity.DEBUG) { - Log.d( - TAG, - "animateBackgroundColor() called with: " + - "view = [" + this + "], duration = [" + duration + "], " + - "colorStart = [" + colorStart + "], colorEnd = [" + colorEnd + "]" + Log.d(TAG, "animateBackgroundColor() called with: view = [$this], duration = [$duration], " + + "colorStart = [$colorStart], colorEnd = [$colorEnd]" ) } - val empty = arrayOf(IntArray(0)) val viewPropertyAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorStart, colorEnd) viewPropertyAnimator.interpolator = FastOutSlowInInterpolator() viewPropertyAnimator.duration = duration - viewPropertyAnimator.addUpdateListener { animation: ValueAnimator -> - ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(animation.animatedValue as Int))) + + fun listenerAction(color: Int) { + ViewCompat.setBackgroundTintList(this, ColorStateList.valueOf(color)) } - viewPropertyAnimator.addListener( - onCancel = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) }, - onEnd = { ViewCompat.setBackgroundTintList(this, ColorStateList(empty, intArrayOf(colorEnd))) } - ) + viewPropertyAnimator.addUpdateListener { listenerAction(it.animatedValue as Int) } + viewPropertyAnimator.addListener(onCancel = { listenerAction(colorEnd) }, onEnd = { listenerAction(colorEnd) }) viewPropertyAnimator.start() } fun View.animateHeight(duration: Long, targetHeight: Int): ValueAnimator { if (MainActivity.DEBUG) { - Log.d( - TAG, - "animateHeight: duration = [" + duration + "], " + - "from " + height + " to → " + targetHeight + " in: " + this - ) + Log.d(TAG, "animateHeight: duration = [$duration], from $height to → $targetHeight in: $this") } val animator = ValueAnimator.ofFloat(height.toFloat(), targetHeight.toFloat()) animator.interpolator = FastOutSlowInInterpolator() animator.duration = duration - animator.addUpdateListener { animation: ValueAnimator -> - val value = animation.animatedValue as Float - layoutParams.height = value.toInt() + + fun listenerAction(value: Int) { + layoutParams.height = value requestLayout() } - animator.addListener( - onCancel = { - layoutParams.height = targetHeight - requestLayout() - }, - onEnd = { - layoutParams.height = targetHeight - requestLayout() - } - ) + animator.addUpdateListener { listenerAction((it.animatedValue as Float).toInt()) } + animator.addListener(onCancel = { listenerAction(targetHeight) }, onEnd = { listenerAction(targetHeight) }) animator.start() return animator } fun View.animateRotation(duration: Long, targetRotation: Int) { if (MainActivity.DEBUG) { - Log.d( - TAG, - "animateRotation: duration = [" + duration + "], " + - "from " + rotation + " to → " + targetRotation + " in: " + this - ) + Log.d(TAG, "animateRotation: duration = [$duration], from $rotation to → $targetRotation in: $this") } animate().setListener(null).cancel() animate() From 4d7a6fb6deb36125a4f74490ebe11e48b92f0a58 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 18 Jul 2022 08:53:29 +0530 Subject: [PATCH 251/992] Use WindowMetrics API in VideoDetailFragment and PopupPlayerUi. --- .../fragments/detail/VideoDetailFragment.java | 11 ++++------ .../newpipe/player/ui/PopupPlayerUi.java | 20 ++++++++++++++----- .../org/schabi/newpipe/util/DeviceUtils.java | 17 ++++++++++++++++ 3 files changed, 36 insertions(+), 12 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 f4838482b..807922166 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 @@ -21,7 +21,6 @@ import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.database.ContentObserver; import android.graphics.Color; -import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -1051,15 +1050,13 @@ public final class VideoDetailFragment // call `post()` to be sure `viewPager.getHitRect()` // is up to date and not being currently recomputed binding.tabLayout.post(() -> { - if (getContext() != null) { + final var activity = getActivity(); + if (activity != null) { final Rect pagerHitRect = new Rect(); binding.viewPager.getHitRect(pagerHitRect); - final Point displaySize = new Point(); - Objects.requireNonNull(ContextCompat.getSystemService(getContext(), - WindowManager.class)).getDefaultDisplay().getSize(displaySize); - - final int viewPagerVisibleHeight = displaySize.y - pagerHitRect.top; + final int height = DeviceUtils.getWindowHeight(activity.getWindowManager()); + final int viewPagerVisibleHeight = height - pagerHitRect.top; // see TabLayout.DEFAULT_HEIGHT, which is equal to 48dp final float tabLayoutHeight = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 48, getResources().getDisplayMetrics()); 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 74adf281c..aa36a6a5a 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 @@ -21,6 +21,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.animation.AnticipateInterpolator; import android.widget.LinearLayout; @@ -255,11 +256,20 @@ public final class PopupPlayerUi extends VideoPlayerUi { } public void updateScreenSize() { - final DisplayMetrics metrics = new DisplayMetrics(); - windowManager.getDefaultDisplay().getMetrics(metrics); - - screenWidth = metrics.widthPixels; - screenHeight = metrics.heightPixels; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final var windowMetrics = windowManager.getCurrentWindowMetrics(); + final var bounds = windowMetrics.getBounds(); + final var windowInsets = windowMetrics.getWindowInsets(); + final var insets = windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars() | WindowInsets.Type.displayCutout()); + screenWidth = bounds.width() - (insets.left + insets.right); + screenHeight = bounds.height() - (insets.top + insets.bottom); + } else { + final DisplayMetrics metrics = new DisplayMetrics(); + windowManager.getDefaultDisplay().getMetrics(metrics); + screenWidth = metrics.widthPixels; + screenHeight = metrics.heightPixels; + } if (DEBUG) { Log.d(TAG, "updateScreenSize() called: screenWidth = [" + screenWidth + "], screenHeight = [" + screenHeight + "]"); diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index 7f4b33f44..3c20dc04b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -4,11 +4,14 @@ import android.app.UiModeManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Point; import android.os.BatteryManager; import android.os.Build; import android.provider.Settings; import android.util.TypedValue; import android.view.KeyEvent; +import android.view.WindowInsets; +import android.view.WindowManager; import androidx.annotation.Dimension; import androidx.annotation.NonNull; @@ -151,4 +154,18 @@ public final class DeviceUtils { Settings.Global.ANIMATOR_DURATION_SCALE, 1F) != 0F; } + + public static int getWindowHeight(@NonNull final WindowManager windowManager) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + final var windowMetrics = windowManager.getCurrentWindowMetrics(); + final var windowInsets = windowMetrics.getWindowInsets(); + final var insets = windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars() | WindowInsets.Type.displayCutout()); + return windowMetrics.getBounds().height() - (insets.top + insets.bottom); + } else { + final Point point = new Point(); + windowManager.getDefaultDisplay().getSize(point); + return point.y; + } + } } From 5c68c8ece8ab36734db7cabeb722fa05ca7f2739 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 13:52:56 +0530 Subject: [PATCH 252/992] Update Lifecycle to 2.5.1. --- app/build.gradle | 6 +++--- .../java/org/schabi/newpipe/local/feed/FeedViewModel.kt | 2 +- .../local/subscription/dialog/FeedGroupDialogViewModel.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..c4a9aa1ee 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -97,7 +97,7 @@ android { ext { checkstyleVersion = '10.3.1' - androidxLifecycleVersion = '2.3.1' + androidxLifecycleVersion = '2.5.1' androidxRoomVersion = '2.4.2' androidxWorkVersion = '2.7.1' @@ -203,8 +203,8 @@ dependencies { implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.fragment:fragment-ktx:1.3.6' - implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" - implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:${androidxLifecycleVersion}" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${androidxLifecycleVersion}" implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' implementation 'androidx.media:media:1.6.0' implementation 'androidx.preference:preference:1.2.0' diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index 7f5ef4301..fd8c5f5de 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -146,7 +146,7 @@ class FeedViewModel( private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { + override fun create(modelClass: Class): T { return FeedViewModel( context.applicationContext, groupId, diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt index 54ba1c6dc..dfdb2b47a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt @@ -122,7 +122,7 @@ class FeedGroupDialogViewModel( private val initialShowOnlyUngrouped: Boolean = false ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { + override fun create(modelClass: Class): T { return FeedGroupDialogViewModel( context.applicationContext, groupId, initialQuery, initialShowOnlyUngrouped From 404c13d4c1d5bc887a45733018a87cee26056fc2 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 26 Jul 2022 17:31:14 +0200 Subject: [PATCH 253/992] Improve FeedViewModel factory --- .../schabi/newpipe/local/feed/FeedFragment.kt | 4 +-- .../newpipe/local/feed/FeedViewModel.kt | 28 ++++++++----------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index f0ebabd85..899163050 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -135,8 +135,8 @@ class FeedFragment : BaseStateFragment() { _feedBinding = FragmentFeedBinding.bind(rootView) super.onViewCreated(rootView, savedInstanceState) - val factory = FeedViewModel.Factory(requireContext(), groupId) - viewModel = ViewModelProvider(this, factory).get(FeedViewModel::class.java) + val factory = FeedViewModel.getFactory(requireContext(), groupId) + viewModel = ViewModelProvider(this, factory)[FeedViewModel::class.java] showPlayedItems = viewModel.getShowPlayedItemsFromPreferences() showFutureItems = viewModel.getShowFutureItemsFromPreferences() viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(::handleResult) } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index fd8c5f5de..896d815bd 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -5,7 +5,8 @@ import androidx.core.content.edit import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import androidx.preference.PreferenceManager import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Flowable @@ -139,21 +140,16 @@ class FeedViewModel( private fun getShowFutureItemsFromPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) .getBoolean(context.getString(R.string.feed_show_future_items_key), true) - } - - class Factory( - private val context: Context, - private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID - ) : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return FeedViewModel( - context.applicationContext, - groupId, - // Read initial value from preferences - getShowPlayedItemsFromPreferences(context.applicationContext), - getShowFutureItemsFromPreferences(context.applicationContext) - ) as T + fun getFactory(context: Context, groupId: Long) = viewModelFactory { + initializer { + FeedViewModel( + context.applicationContext, + groupId, + // Read initial value from preferences + getShowPlayedItemsFromPreferences(context.applicationContext), + getShowFutureItemsFromPreferences(context.applicationContext) + ) + } } } } From 311d392386696a4185405d80f8f271f17005da7f Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 08:35:50 +0530 Subject: [PATCH 254/992] Use Application instead of Context in FeedViewModel. --- .../newpipe/local/feed/FeedViewModel.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt index 896d815bd..76d5e9d63 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedViewModel.kt @@ -1,5 +1,6 @@ package org.schabi.newpipe.local.feed +import android.app.Application import android.content.Context import androidx.core.content.edit import androidx.lifecycle.LiveData @@ -13,6 +14,7 @@ import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.functions.Function5 import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.schedulers.Schedulers +import org.schabi.newpipe.App import org.schabi.newpipe.R import org.schabi.newpipe.database.feed.model.FeedGroupEntity import org.schabi.newpipe.database.stream.StreamWithState @@ -27,12 +29,12 @@ import java.time.OffsetDateTime import java.util.concurrent.TimeUnit class FeedViewModel( - private val applicationContext: Context, + private val application: Application, groupId: Long = FeedGroupEntity.GROUP_ALL_ID, initialShowPlayedItems: Boolean = true, initialShowFutureItems: Boolean = true ) : ViewModel() { - private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext) + private val feedDatabaseManager = FeedDatabaseManager(application) private val toggleShowPlayedItems = BehaviorProcessor.create() private val toggleShowPlayedItemsFlowable = toggleShowPlayedItems @@ -114,24 +116,24 @@ class FeedViewModel( } fun saveShowPlayedItemsToPreferences(showPlayedItems: Boolean) = - PreferenceManager.getDefaultSharedPreferences(applicationContext).edit { - this.putBoolean(applicationContext.getString(R.string.feed_show_played_items_key), showPlayedItems) + PreferenceManager.getDefaultSharedPreferences(application).edit { + this.putBoolean(application.getString(R.string.feed_show_played_items_key), showPlayedItems) this.apply() } - fun getShowPlayedItemsFromPreferences() = getShowPlayedItemsFromPreferences(applicationContext) + fun getShowPlayedItemsFromPreferences() = getShowPlayedItemsFromPreferences(application) fun toggleFutureItems(showFutureItems: Boolean) { toggleShowFutureItems.onNext(showFutureItems) } fun saveShowFutureItemsToPreferences(showFutureItems: Boolean) = - PreferenceManager.getDefaultSharedPreferences(applicationContext).edit { - this.putBoolean(applicationContext.getString(R.string.feed_show_future_items_key), showFutureItems) + PreferenceManager.getDefaultSharedPreferences(application).edit { + this.putBoolean(application.getString(R.string.feed_show_future_items_key), showFutureItems) this.apply() } - fun getShowFutureItemsFromPreferences() = getShowFutureItemsFromPreferences(applicationContext) + fun getShowFutureItemsFromPreferences() = getShowFutureItemsFromPreferences(application) companion object { private fun getShowPlayedItemsFromPreferences(context: Context) = @@ -143,7 +145,7 @@ class FeedViewModel( fun getFactory(context: Context, groupId: Long) = viewModelFactory { initializer { FeedViewModel( - context.applicationContext, + App.getApp(), groupId, // Read initial value from preferences getShowPlayedItemsFromPreferences(context.applicationContext), From 582032f37299cdfdf6854d00718e96646bb07d2b Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Sun, 31 Jul 2022 00:14:23 -0400 Subject: [PATCH 255/992] Update AndroidX Room to 2.4.3 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 0585120ce..c77335ad9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -98,7 +98,7 @@ ext { checkstyleVersion = '10.3.1' androidxLifecycleVersion = '2.3.1' - androidxRoomVersion = '2.4.2' + androidxRoomVersion = '2.4.3' androidxWorkVersion = '2.7.1' icepickVersion = '3.2.0' From 47f58040d1fc843ba8f4b7abcc1d40b1f9852cc1 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 13:38:24 +0530 Subject: [PATCH 256/992] Make OnClickGesture an interface. --- .../fragments/list/BaseListFragment.java | 58 ++++++------------- .../local/bookmark/BookmarkFragment.java | 2 +- .../local/dialog/PlaylistAppendDialog.java | 22 ++----- .../history/StatisticsPlaylistFragment.java | 2 +- .../local/playlist/LocalPlaylistFragment.java | 2 +- .../subscription/SubscriptionFragment.kt | 4 +- .../schabi/newpipe/util/OnClickGesture.java | 9 ++- 7 files changed, 32 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index 27e5a8571..9e7cb757c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -23,14 +23,11 @@ import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.R; import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.channel.ChannelInfoItem; -import org.schabi.newpipe.extractor.comments.CommentsInfoItem; -import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; -import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.info_list.InfoListAdapter; +import org.schabi.newpipe.info_list.dialog.InfoItemDialog; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.StateSaver; @@ -264,45 +261,28 @@ public abstract class BaseListFragment extends BaseStateFragment } }); - infoListAdapter.setOnChannelSelectedListener(new OnClickGesture<>() { - @Override - public void selected(final ChannelInfoItem selectedItem) { - try { - onItemSelected(selectedItem); - NavigationHelper.openChannelFragment(getFM(), - selectedItem.getServiceId(), - selectedItem.getUrl(), - selectedItem.getName()); - } catch (final Exception e) { - ErrorUtil.showUiErrorSnackbar( - BaseListFragment.this, "Opening channel fragment", e); - } - } - }); - - infoListAdapter.setOnPlaylistSelectedListener(new OnClickGesture<>() { - @Override - public void selected(final PlaylistInfoItem selectedItem) { - try { - onItemSelected(selectedItem); - NavigationHelper.openPlaylistFragment(getFM(), - selectedItem.getServiceId(), - selectedItem.getUrl(), - selectedItem.getName()); - } catch (final Exception e) { - ErrorUtil.showUiErrorSnackbar(BaseListFragment.this, - "Opening playlist fragment", e); - } - } - }); - - infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture<>() { - @Override - public void selected(final CommentsInfoItem selectedItem) { + infoListAdapter.setOnChannelSelectedListener(selectedItem -> { + try { onItemSelected(selectedItem); + NavigationHelper.openChannelFragment(getFM(), selectedItem.getServiceId(), + selectedItem.getUrl(), selectedItem.getName()); + } catch (final Exception e) { + ErrorUtil.showUiErrorSnackbar(this, "Opening channel fragment", e); } }); + infoListAdapter.setOnPlaylistSelectedListener(selectedItem -> { + try { + onItemSelected(selectedItem); + NavigationHelper.openPlaylistFragment(getFM(), selectedItem.getServiceId(), + selectedItem.getUrl(), selectedItem.getName()); + } catch (final Exception e) { + ErrorUtil.showUiErrorSnackbar(this, "Opening playlist fragment", e); + } + }); + + infoListAdapter.setOnCommentsSelectedListener(this::onItemSelected); + // Ensure that there is always a scroll listener (e.g. when rotating the device) useNormalItemListScrollListener(); } diff --git a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java index ac11d007f..be7414542 100644 --- a/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java @@ -98,7 +98,7 @@ public final class BookmarkFragment extends BaseLocalListFragment() { + itemListAdapter.setSelectedListener(new OnClickGesture<>() { @Override public void selected(final LocalItem selectedItem) { final FragmentManager fragmentManager = getFM(); diff --git a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java index a97eb0c18..3d5d16c39 100644 --- a/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java +++ b/app/src/main/java/org/schabi/newpipe/local/dialog/PlaylistAppendDialog.java @@ -13,12 +13,10 @@ import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; -import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.database.stream.model.StreamEntity; import org.schabi.newpipe.local.LocalItemListAdapter; import org.schabi.newpipe.local.playlist.LocalPlaylistManager; -import org.schabi.newpipe.util.OnClickGesture; import java.util.List; @@ -63,18 +61,10 @@ public final class PlaylistAppendDialog extends PlaylistDialog { new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext())); playlistAdapter = new LocalItemListAdapter(getActivity()); - playlistAdapter.setSelectedListener(new OnClickGesture() { - @Override - public void selected(final LocalItem selectedItem) { - if (!(selectedItem instanceof PlaylistMetadataEntry) - || getStreamEntities() == null) { - return; - } - onPlaylistSelected( - playlistManager, - (PlaylistMetadataEntry) selectedItem, - getStreamEntities() - ); + playlistAdapter.setSelectedListener(selectedItem -> { + final List entities = getStreamEntities(); + if (selectedItem instanceof PlaylistMetadataEntry && entities != null) { + onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem, entities); } }); @@ -138,10 +128,6 @@ public final class PlaylistAppendDialog extends PlaylistDialog { private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager, @NonNull final PlaylistMetadataEntry playlist, @NonNull final List streams) { - if (getStreamEntities() == null) { - return; - } - final Toast successToast = Toast.makeText(getContext(), R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java index 01df34292..a20a80ae9 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java @@ -135,7 +135,7 @@ public class StatisticsPlaylistFragment protected void initListeners() { super.initListeners(); - itemListAdapter.setSelectedListener(new OnClickGesture() { + itemListAdapter.setSelectedListener(new OnClickGesture<>() { @Override public void selected(final LocalItem selectedItem) { if (selectedItem instanceof StreamStatisticsEntry) { 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 fa789d97d..11d54f1ef 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 @@ -167,7 +167,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment() { + itemListAdapter.setSelectedListener(new OnClickGesture<>() { @Override public void selected(final LocalItem selectedItem) { if (selectedItem instanceof PlaylistStreamEntry) { diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt index 4295424e6..20f8a01c1 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt @@ -346,7 +346,7 @@ class SubscriptionFragment : BaseStateFragment() { override fun doInitialLoadLogic() = Unit override fun startLoading(forceLoad: Boolean) = Unit - private val listenerFeedGroups = object : OnClickGesture>() { + private val listenerFeedGroups = object : OnClickGesture> { override fun selected(selectedItem: Item<*>?) { when (selectedItem) { is FeedGroupCardItem -> NavigationHelper.openFeedFragment(fm, selectedItem.groupId, selectedItem.name) @@ -361,7 +361,7 @@ class SubscriptionFragment : BaseStateFragment() { } } - private val listenerChannelItem = object : OnClickGesture() { + private val listenerChannelItem = object : OnClickGesture { override fun selected(selectedItem: ChannelInfoItem) = NavigationHelper.openChannelFragment( fm, selectedItem.serviceId, selectedItem.url, selectedItem.name diff --git a/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java b/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java index 5f44cab8b..ae8d86af1 100644 --- a/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java +++ b/app/src/main/java/org/schabi/newpipe/util/OnClickGesture.java @@ -2,15 +2,14 @@ package org.schabi.newpipe.util; import androidx.recyclerview.widget.RecyclerView; -public abstract class OnClickGesture { +public interface OnClickGesture { + void selected(T selectedItem); - public abstract void selected(T selectedItem); - - public void held(final T selectedItem) { + default void held(final T selectedItem) { // Optional gesture } - public void drag(final T selectedItem, final RecyclerView.ViewHolder viewHolder) { + default void drag(final T selectedItem, final RecyclerView.ViewHolder viewHolder) { // Optional gesture } } From 8a896114c110ba4fb6441465fbb88317e597666f Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 1 Aug 2022 08:25:24 +0530 Subject: [PATCH 257/992] Apply code review change. --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 4ad270c50..9d13cb931 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 @@ -192,6 +192,7 @@ public final class VideoDetailFragment private Disposable positionSubscriber = null; private BottomSheetBehavior bottomSheetBehavior; + private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback; private BroadcastReceiver broadcastReceiver; /*////////////////////////////////////////////////////////////////////////// @@ -208,8 +209,6 @@ public final class VideoDetailFragment private Player player; private final PlayerHolder playerHolder = PlayerHolder.getInstance(); - private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback; - /*////////////////////////////////////////////////////////////////////////// // Service management //////////////////////////////////////////////////////////////////////////*/ From 947242d9134d20696c72b98b39cd098b30fc1029 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 16 Jul 2022 06:33:53 +0530 Subject: [PATCH 258/992] Update AppCompat to 1.4.2. --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index c4a9aa1ee..d672940a9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}" /** AndroidX **/ - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.core:core-ktx:1.8.0' From 013522c3766816db2bb23c144e7fdd36b1562aaa Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 05:20:06 +0530 Subject: [PATCH 259/992] Convert LicenseFragmentHelper methods to top-level declarations. --- .../schabi/newpipe/about/LicenseFragment.kt | 1 - .../newpipe/about/LicenseFragmentHelper.kt | 151 +++++++++--------- 2 files changed, 75 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt index c816d78be..f19ecd74a 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt @@ -8,7 +8,6 @@ import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import io.reactivex.rxjava3.disposables.CompositeDisposable import org.schabi.newpipe.R -import org.schabi.newpipe.about.LicenseFragmentHelper.showLicense import org.schabi.newpipe.databinding.FragmentLicensesBinding import org.schabi.newpipe.databinding.ItemSoftwareComponentBinding diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index 3acb50cb9..34dfe8fb4 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -14,30 +14,29 @@ import org.schabi.newpipe.util.ThemeHelper import org.schabi.newpipe.util.external_communication.ShareUtils import java.io.IOException -object LicenseFragmentHelper { - /** - * @param context the context to use - * @param license the license - * @return String which contains a HTML formatted license page - * styled according to the context's theme - */ - private fun getFormattedLicense(context: Context, license: License): String { - try { - return context.assets.open(license.filename).bufferedReader().use { it.readText() } - // split the HTML file and insert the stylesheet into the HEAD of the file - .replace("", "") - } catch (e: IOException) { - throw IllegalArgumentException("Could not get license file: ${license.filename}", e) - } +/** + * @param context the context to use + * @param license the license + * @return String which contains a HTML formatted license page + * styled according to the context's theme + */ +private fun getFormattedLicense(context: Context, license: License): String { + try { + return context.assets.open(license.filename).bufferedReader().use { it.readText() } + // split the HTML file and insert the stylesheet into the HEAD of the file + .replace("", "") + } catch (e: IOException) { + throw IllegalArgumentException("Could not get license file: ${license.filename}", e) } +} - /** - * @param context the Android context - * @return String which is a CSS stylesheet according to the context's theme - */ - private fun getLicenseStylesheet(context: Context): String { - val isLightTheme = ThemeHelper.isLightThemeSelected(context) - return ( +/** + * @param context the Android context + * @return String which is a CSS stylesheet according to the context's theme + */ +private fun getLicenseStylesheet(context: Context): String { + val isLightTheme = ThemeHelper.isLightThemeSelected(context) + return ( "body{padding:12px 15px;margin:0;" + "background:#" + getHexRGBColor( context, if (isLightTheme) R.color.light_license_background_color @@ -52,62 +51,62 @@ object LicenseFragmentHelper { else R.color.dark_youtube_primary_color ) + "}" + "pre{white-space:pre-wrap}" ) - } +} - /** - * Cast R.color to a hexadecimal color value. - * - * @param context the context to use - * @param color the color number from R.color - * @return a six characters long String with hexadecimal RGB values - */ - private fun getHexRGBColor(context: Context, color: Int): String { - return context.getString(color).substring(3) - } +/** + * Cast R.color to a hexadecimal color value. + * + * @param context the context to use + * @param color the color number from R.color + * @return a six characters long String with hexadecimal RGB values + */ +private fun getHexRGBColor(context: Context, color: Int): String { + return context.getString(color).substring(3) +} - fun showLicense(context: Context?, license: License): Disposable { - return showLicense(context, license) { alertDialog -> - alertDialog.setPositiveButton(R.string.ok) { dialog, _ -> - dialog.dismiss() - } - } - } - - fun showLicense(context: Context?, component: SoftwareComponent): Disposable { - return showLicense(context, component.license) { alertDialog -> - alertDialog.setPositiveButton(R.string.dismiss) { dialog, _ -> - dialog.dismiss() - } - alertDialog.setNeutralButton(R.string.open_website_license) { _, _ -> - ShareUtils.openUrlInBrowser(context!!, component.link) - } - } - } - - private fun showLicense( - context: Context?, - license: License, - block: (AlertDialog.Builder) -> Unit - ): Disposable { - return if (context == null) { - Disposable.empty() - } else { - Observable.fromCallable { getFormattedLicense(context, license) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { formattedLicense -> - val webViewData = Base64.encodeToString(formattedLicense.toByteArray(), Base64.NO_PADDING) - val webView = WebView(context) - webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") - - AlertDialog.Builder(context).apply { - setTitle(license.name) - setView(webView) - Localization.assureCorrectAppLanguage(context) - block(this) - show() - } - } +fun showLicense(context: Context?, license: License): Disposable { + return showLicense(context, license) { alertDialog -> + alertDialog.setPositiveButton(R.string.ok) { dialog, _ -> + dialog.dismiss() } } } + +fun showLicense(context: Context?, component: SoftwareComponent): Disposable { + return showLicense(context, component.license) { alertDialog -> + alertDialog.setPositiveButton(R.string.dismiss) { dialog, _ -> + dialog.dismiss() + } + alertDialog.setNeutralButton(R.string.open_website_license) { _, _ -> + ShareUtils.openUrlInBrowser(context!!, component.link) + } + } +} + +private fun showLicense( + context: Context?, + license: License, + block: (AlertDialog.Builder) -> Unit +): Disposable { + return if (context == null) { + Disposable.empty() + } else { + Observable.fromCallable { getFormattedLicense(context, license) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { formattedLicense -> + val webViewData = + Base64.encodeToString(formattedLicense.toByteArray(), Base64.NO_PADDING) + val webView = WebView(context) + webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") + + AlertDialog.Builder(context).apply { + setTitle(license.name) + setView(webView) + Localization.assureCorrectAppLanguage(context) + block(this) + show() + } + } + } +} From a9095ca2ad3abcce665d14a609973a3410f39f1d Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 05:38:33 +0530 Subject: [PATCH 260/992] Make block parameter an extension lambda. --- .../newpipe/about/LicenseFragmentHelper.kt | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index 34dfe8fb4..05873c05d 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -64,29 +64,25 @@ private fun getHexRGBColor(context: Context, color: Int): String { return context.getString(color).substring(3) } -fun showLicense(context: Context?, license: License): Disposable { - return showLicense(context, license) { alertDialog -> - alertDialog.setPositiveButton(R.string.ok) { dialog, _ -> - dialog.dismiss() - } - } -} - fun showLicense(context: Context?, component: SoftwareComponent): Disposable { - return showLicense(context, component.license) { alertDialog -> - alertDialog.setPositiveButton(R.string.dismiss) { dialog, _ -> + return showLicense(context, component.license) { + setPositiveButton(R.string.dismiss) { dialog, _ -> dialog.dismiss() } - alertDialog.setNeutralButton(R.string.open_website_license) { _, _ -> + setNeutralButton(R.string.open_website_license) { _, _ -> ShareUtils.openUrlInBrowser(context!!, component.link) } } } +fun showLicense(context: Context?, license: License) = showLicense(context, license) { + setPositiveButton(R.string.ok) { dialog, _ -> dialog.dismiss() } +} + private fun showLicense( context: Context?, license: License, - block: (AlertDialog.Builder) -> Unit + block: AlertDialog.Builder.() -> AlertDialog.Builder ): Disposable { return if (context == null) { Disposable.empty() @@ -100,13 +96,12 @@ private fun showLicense( val webView = WebView(context) webView.loadData(webViewData, "text/html; charset=UTF-8", "base64") - AlertDialog.Builder(context).apply { - setTitle(license.name) - setView(webView) - Localization.assureCorrectAppLanguage(context) - block(this) - show() - } + Localization.assureCorrectAppLanguage(context) + AlertDialog.Builder(context) + .setTitle(license.name) + .setView(webView) + .block() + .show() } } } From 8dce66d76f821f46875bf0e18a0308fd924284df Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 4 Aug 2022 10:49:33 +0200 Subject: [PATCH 261/992] Improve FeedGroupDialogViewModel factory --- .../subscription/dialog/FeedGroupDialog.kt | 8 +++--- .../dialog/FeedGroupDialogViewModel.kt | 27 ++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt index 379b4c0d7..4b3c4ccc0 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt @@ -124,11 +124,13 @@ class FeedGroupDialog : DialogFragment(), BackPressable { viewModel = ViewModelProvider( this, - FeedGroupDialogViewModel.Factory( + FeedGroupDialogViewModel.getFactory( requireContext(), - groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped + groupId, + subscriptionsCurrentSearchQuery, + subscriptionsShowOnlyUngrouped ) - ).get(FeedGroupDialogViewModel::class.java) + )[FeedGroupDialogViewModel::class.java] viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup)) viewModel.subscriptionsLiveData.observe(viewLifecycleOwner) { diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt index dfdb2b47a..eff1a4400 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialogViewModel.kt @@ -4,7 +4,8 @@ import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.disposables.Disposable @@ -115,18 +116,18 @@ class FeedGroupDialogViewModel( data class Filter(val query: String, val showOnlyUngrouped: Boolean) - class Factory( - private val context: Context, - private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID, - private val initialQuery: String = "", - private val initialShowOnlyUngrouped: Boolean = false - ) : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return FeedGroupDialogViewModel( - context.applicationContext, - groupId, initialQuery, initialShowOnlyUngrouped - ) as T + companion object { + fun getFactory( + context: Context, + groupId: Long, + initialQuery: String, + initialShowOnlyUngrouped: Boolean + ) = viewModelFactory { + initializer { + FeedGroupDialogViewModel( + context.applicationContext, groupId, initialQuery, initialShowOnlyUngrouped + ) + } } } } From 4b7de86a92bfe22a20a017b615523a64e1bb9e80 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 23 Jul 2022 06:00:08 +0530 Subject: [PATCH 262/992] Clean up getLicenseStylesheet(). --- .../newpipe/about/LicenseFragmentHelper.kt | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt index 05873c05d..6e3aa4be8 100644 --- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt @@ -36,21 +36,17 @@ private fun getFormattedLicense(context: Context, license: License): String { */ private fun getLicenseStylesheet(context: Context): String { val isLightTheme = ThemeHelper.isLightThemeSelected(context) - return ( - "body{padding:12px 15px;margin:0;" + "background:#" + getHexRGBColor( - context, - if (isLightTheme) R.color.light_license_background_color - else R.color.dark_license_background_color - ) + ";" + "color:#" + getHexRGBColor( - context, - if (isLightTheme) R.color.light_license_text_color - else R.color.dark_license_text_color - ) + "}" + "a[href]{color:#" + getHexRGBColor( - context, - if (isLightTheme) R.color.light_youtube_primary_color - else R.color.dark_youtube_primary_color - ) + "}" + "pre{white-space:pre-wrap}" - ) + val licenseBackgroundColor = getHexRGBColor( + context, if (isLightTheme) R.color.light_license_background_color else R.color.dark_license_background_color + ) + val licenseTextColor = getHexRGBColor( + context, if (isLightTheme) R.color.light_license_text_color else R.color.dark_license_text_color + ) + val youtubePrimaryColor = getHexRGBColor( + context, if (isLightTheme) R.color.light_youtube_primary_color else R.color.dark_youtube_primary_color + ) + return "body{padding:12px 15px;margin:0;background:#$licenseBackgroundColor;color:#$licenseTextColor}" + + "a[href]{color:#$youtubePrimaryColor}pre{white-space:pre-wrap}" } /** From 059cfcbad26422bff114cab9e94da03219243d70 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 3 Aug 2022 04:38:47 +0530 Subject: [PATCH 263/992] Use Comparator factory methods in ListHelper. --- .../org/schabi/newpipe/util/ListHelper.java | 62 ++++--------------- 1 file changed, 12 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index fbbe43513..45234706b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -343,7 +343,10 @@ public final class ListHelper { */ private static List sortStreamList(final List videoStreams, final boolean ascendingOrder) { - final Comparator comparator = ListHelper::compareVideoStreamResolution; + // Compares the quality of two video streams. + final Comparator comparator = Comparator.nullsLast(Comparator + .comparing(VideoStream::getResolution, ListHelper::compareVideoStreamResolution) + .thenComparingInt(s -> VIDEO_FORMAT_QUALITY_RANKING.indexOf(s.getFormat()))); Collections.sort(videoStreams, ascendingOrder ? comparator : comparator.reversed()); return videoStreams; } @@ -360,8 +363,7 @@ public final class ListHelper { @Nullable final List audioStreams) { return getAudioIndexByHighestRank(format, audioStreams, // Compares descending (last = highest rank) - (s1, s2) -> compareAudioStreamBitrate(s1, s2, AUDIO_FORMAT_QUALITY_RANKING) - ); + getAudioStreamComparator(AUDIO_FORMAT_QUALITY_RANKING)); } /** @@ -374,11 +376,15 @@ public final class ListHelper { */ static int getMostCompactAudioIndex(@Nullable final MediaFormat format, @Nullable final List audioStreams) { - return getAudioIndexByHighestRank(format, audioStreams, // The "-" is important -> Compares ascending (first = highest rank) - (s1, s2) -> -compareAudioStreamBitrate(s1, s2, AUDIO_FORMAT_EFFICIENCY_RANKING) - ); + getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING).reversed()); + } + + private static Comparator getAudioStreamComparator( + final List formatRanking) { + return Comparator.nullsLast(Comparator.comparingInt(AudioStream::getAverageBitrate)) + .thenComparingInt(stream -> formatRanking.indexOf(stream.getFormat())); } /** @@ -544,28 +550,6 @@ public final class ListHelper { return format; } - // Compares the quality of two audio streams - private static int compareAudioStreamBitrate(final AudioStream streamA, - final AudioStream streamB, - final List formatRanking) { - if (streamA == null) { - return -1; - } - if (streamB == null) { - return 1; - } - if (streamA.getAverageBitrate() < streamB.getAverageBitrate()) { - return -1; - } - if (streamA.getAverageBitrate() > streamB.getAverageBitrate()) { - return 1; - } - - // Same bitrate and format - return formatRanking.indexOf(streamA.getFormat()) - - formatRanking.indexOf(streamB.getFormat()); - } - private static int compareVideoStreamResolution(@NonNull final String r1, @NonNull final String r2) { try { @@ -582,28 +566,6 @@ public final class ListHelper { } } - // Compares the quality of two video streams. - private static int compareVideoStreamResolution(final VideoStream streamA, - final VideoStream streamB) { - if (streamA == null) { - return -1; - } - if (streamB == null) { - return 1; - } - - final int resComp = compareVideoStreamResolution(streamA.getResolution(), - streamB.getResolution()); - if (resComp != 0) { - return resComp; - } - - // Same bitrate and format - return ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamA.getFormat()) - - ListHelper.VIDEO_FORMAT_QUALITY_RANKING.indexOf(streamB.getFormat()); - } - - private static boolean isLimitingDataUsage(final Context context) { return getResolutionLimit(context) != null; } From d1f3f15478bada81a2172b45f6ba4385bc0ff7a7 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 3 Aug 2022 06:01:32 +0530 Subject: [PATCH 264/992] Use Comparator.comparingDouble(). --- .../preferencesearch/PreferenceFuzzySearchFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java index 7c231cafb..ea45c68d2 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java @@ -31,7 +31,7 @@ public class PreferenceFuzzySearchFunction // Specific search - Used for determining order of search results // Calculate a score based on specific search fields .map(item -> new FuzzySearchSpecificDTO(item, keyword)) - .sorted(Comparator.comparing(FuzzySearchSpecificDTO::getScore).reversed()) + .sorted(Comparator.comparingDouble(FuzzySearchSpecificDTO::getScore).reversed()) .map(FuzzySearchSpecificDTO::getItem) // Limit the amount of search results .limit(20); From 8024b437e9dfeeec29be83b7b6d0049f53d5090f Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 30 Jul 2022 01:50:23 +0530 Subject: [PATCH 265/992] Add reusable classes extending AnimatorListenerAdapter. --- .../main/java/org/schabi/newpipe/ktx/View.kt | 99 +++++++------------ 1 file changed, 38 insertions(+), 61 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 5ab8dbdc2..bf0dcb201 100644 --- a/app/src/main/java/org/schabi/newpipe/ktx/View.kt +++ b/app/src/main/java/org/schabi/newpipe/ktx/View.kt @@ -90,7 +90,9 @@ fun View.animate( */ fun View.animateBackgroundColor(duration: Long, @ColorInt colorStart: Int, @ColorInt colorEnd: Int) { if (MainActivity.DEBUG) { - Log.d(TAG, "animateBackgroundColor() called with: view = [$this], duration = [$duration], " + + Log.d( + TAG, + "animateBackgroundColor() called with: view = [$this], duration = [$duration], " + "colorStart = [$colorStart], colorEnd = [$colorEnd]" ) } @@ -147,20 +149,13 @@ private fun View.animateAlpha(enterOrExit: Boolean, duration: Long, delay: Long, if (enterOrExit) { animate().setInterpolator(FastOutSlowInInterpolator()).alpha(1f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { animate().setInterpolator(FastOutSlowInInterpolator()).alpha(0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -172,11 +167,8 @@ private fun View.animateScaleAndAlpha(enterOrExit: Boolean, duration: Long, dela .setInterpolator(FastOutSlowInInterpolator()) .alpha(1f).scaleX(1f).scaleY(1f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { scaleX = 1f scaleY = 1f @@ -184,12 +176,8 @@ private fun View.animateScaleAndAlpha(enterOrExit: Boolean, duration: Long, dela .setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).scaleX(.8f).scaleY(.8f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -202,11 +190,8 @@ private fun View.animateLightScaleAndAlpha(enterOrExit: Boolean, duration: Long, .setInterpolator(FastOutSlowInInterpolator()) .alpha(1f).scaleX(1f).scaleY(1f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { alpha = 1f scaleX = 1f @@ -215,12 +200,8 @@ private fun View.animateLightScaleAndAlpha(enterOrExit: Boolean, duration: Long, .setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).scaleX(.95f).scaleY(.95f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -231,22 +212,15 @@ private fun View.animateSlideAndAlpha(enterOrExit: Boolean, duration: Long, dela animate() .setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { animate() .setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).translationY(-height.toFloat()) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -257,21 +231,14 @@ private fun View.animateLightSlideAndAlpha(enterOrExit: Boolean, duration: Long, animate() .setInterpolator(FastOutSlowInInterpolator()).alpha(1f).translationY(0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }).start() + .setListener(ExecOnEndListener(execOnEnd)) + .start() } else { animate().setInterpolator(FastOutSlowInInterpolator()) .alpha(0f).translationY(-height / 2.0f) .setDuration(duration).setStartDelay(delay) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - isGone = true - execOnEnd?.run() - } - }).start() + .setListener(HideAndExecOnEndListener(this, execOnEnd)) + .start() } } @@ -293,11 +260,7 @@ fun View.slideUp( .setStartDelay(delay) .setDuration(duration) .setInterpolator(FastOutSlowInInterpolator()) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - execOnEnd?.run() - } - }) + .setListener(ExecOnEndListener(execOnEnd)) .start() } @@ -311,6 +274,20 @@ fun View.animateHideRecyclerViewAllowingScrolling() { animate().alpha(0.0f).setDuration(200).start() } +private open class ExecOnEndListener(private val execOnEnd: Runnable?) : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + execOnEnd?.run() + } +} + +private class HideAndExecOnEndListener(private val view: View, execOnEnd: Runnable?) : + ExecOnEndListener(execOnEnd) { + override fun onAnimationEnd(animation: Animator) { + view.isGone = true + super.onAnimationEnd(animation) + } +} + enum class AnimationType { ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA } From 2eec2e9128924bbe708eeb4958191b0d3fe3ff6c Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 5 Aug 2022 06:19:06 +0530 Subject: [PATCH 266/992] Replace coerceIn() with MathUtils.clamp(). --- .../newpipe/player/gesture/PopupPlayerGestureListener.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt index 01b15f30a..666ea6a46 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/PopupPlayerGestureListener.kt @@ -4,6 +4,7 @@ import android.util.Log import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration +import androidx.core.math.MathUtils import org.schabi.newpipe.MainActivity import org.schabi.newpipe.ktx.AnimationType import org.schabi.newpipe.ktx.animate @@ -234,11 +235,13 @@ class PopupPlayerGestureListener( isMoving = true val diffX = (movingEvent.rawX - initialEvent.rawX) - val posX = (initialPopupX + diffX).coerceIn( + val posX = MathUtils.clamp( + initialPopupX + diffX, 0f, (playerUi.screenWidth - playerUi.popupLayoutParams.width).toFloat() ) val diffY = (movingEvent.rawY - initialEvent.rawY) - val posY = (initialPopupY + diffY).coerceIn( + val posY = MathUtils.clamp( + initialPopupY + diffY, 0f, (playerUi.screenHeight - playerUi.popupLayoutParams.height).toFloat() ) From fc46233baf5827250b852d2118f15ea89b7ef132 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 30 Jul 2022 00:51:15 +0530 Subject: [PATCH 267/992] Use toArray() with zero-length arrays. --- .../FragmentStatePagerAdapterMenuWorkaround.java | 6 ++---- .../fragments/detail/VideoDetailFragment.java | 8 ++------ .../fragments/list/search/SearchFragment.java | 5 ++--- .../player/notification/NotificationConstants.java | 5 ++--- .../player/notification/NotificationUtil.java | 5 +---- .../custom/NotificationActionsPreference.java | 13 ++++++------- .../java/org/schabi/newpipe/streams/WebMReader.java | 3 +-- 7 files changed, 16 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java b/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java index 639443377..8d87e90bd 100644 --- a/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java +++ b/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.java @@ -282,11 +282,9 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt @Nullable public Parcelable saveState() { Bundle state = null; - if (mSavedState.size() > 0) { + if (!mSavedState.isEmpty()) { state = new Bundle(); - final Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; - mSavedState.toArray(fss); - state.putParcelableArray("states", fss); + state.putParcelableArray("states", mSavedState.toArray(new Fragment.SavedState[0])); } for (int i = 0; i < mFragments.size(); i++) { final Fragment f = mFragments.get(i); 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 f0672cd41..3b1bdaede 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 @@ -2167,12 +2167,8 @@ public final class VideoDetailFragment } else { final int selectedVideoStreamIndexForExternalPlayers = ListHelper.getDefaultResolutionIndex(activity, videoStreamsForExternalPlayers); - final CharSequence[] resolutions = - new CharSequence[videoStreamsForExternalPlayers.size()]; - - for (int i = 0; i < videoStreamsForExternalPlayers.size(); i++) { - resolutions[i] = videoStreamsForExternalPlayers.get(i).getResolution(); - } + final CharSequence[] resolutions = videoStreamsForExternalPlayers.stream() + .map(VideoStream::getResolution).toArray(CharSequence[]::new); builder.setSingleChoiceItems(resolutions, selectedVideoStreamIndexForExternalPlayers, null); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 44f8328a5..008163890 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -919,7 +919,7 @@ public class SearchFragment extends BaseListFragment cannot be bundled without creating some containers - metaInfo = new MetaInfo[result.getMetaInfo().size()]; - metaInfo = result.getMetaInfo().toArray(metaInfo); + metaInfo = result.getMetaInfo().toArray(new MetaInfo[0]); showMetaInfoInTextView(result.getMetaInfo(), searchBinding.searchMetaInfoTextView, searchBinding.searchMetaInfoSeparator, disposables); diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index b8e39e564..7015e0717 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -14,7 +14,6 @@ import org.schabi.newpipe.util.Localization; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; @@ -115,7 +114,7 @@ public final class NotificationConstants { }; - public static final Integer[] SLOT_COMPACT_DEFAULTS = {0, 1, 2}; + public static final List SLOT_COMPACT_DEFAULTS = List.of(0, 1, 2); public static final int[] SLOT_COMPACT_PREF_KEYS = { R.string.notification_slot_compact_0_key, @@ -181,7 +180,7 @@ public final class NotificationConstants { if (compactSlot == Integer.MAX_VALUE) { // settings not yet populated, return default values - return new ArrayList<>(Arrays.asList(SLOT_COMPACT_DEFAULTS)); + return SLOT_COMPACT_DEFAULTS; } // a negative value (-1) is set when the user does not want a particular compact slot diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 2ba754500..ef225b14e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -99,10 +99,7 @@ public final class NotificationUtil { // build the compact slot indices array (need code to convert from Integer... because Java) final List compactSlotList = NotificationConstants.getCompactSlotsFromPreferences( player.getContext(), player.getPrefs(), nonNothingSlotCount); - final int[] compactSlots = new int[compactSlotList.size()]; - for (int i = 0; i < compactSlotList.size(); i++) { - compactSlots[i] = compactSlotList.get(i); - } + final int[] compactSlots = compactSlotList.stream().mapToInt(i -> i).toArray(); builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() .setMediaSession(player.getMediaSessionManager().getSessionToken()) diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 03b5a5a95..1101f410d 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -34,7 +34,9 @@ import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; +import java.util.ArrayList; import java.util.List; +import java.util.stream.IntStream; public class NotificationActionsPreference extends Preference { @@ -74,13 +76,10 @@ public class NotificationActionsPreference extends Preference { //////////////////////////////////////////////////////////////////////////// private void setupActions(@NonNull final View view) { - compactSlots = - NotificationConstants.getCompactSlotsFromPreferences( - getContext(), getSharedPreferences(), 5); - notificationSlots = new NotificationSlot[5]; - for (int i = 0; i < 5; i++) { - notificationSlots[i] = new NotificationSlot(i, view); - } + compactSlots = new ArrayList<>(NotificationConstants + .getCompactSlotsFromPreferences(getContext(), getSharedPreferences(), 5)); + notificationSlots = IntStream.range(0, 5).mapToObj(i -> new NotificationSlot(i, view)) + .toArray(NotificationSlot[]::new); } diff --git a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java index 8253ad6af..678974cce 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java +++ b/app/src/main/java/org/schabi/newpipe/streams/WebMReader.java @@ -348,8 +348,7 @@ public class WebMReader { ensure(elemTrackEntry); } - final WebMTrack[] entries = new WebMTrack[trackEntries.size()]; - trackEntries.toArray(entries); + final WebMTrack[] entries = trackEntries.toArray(new WebMTrack[0]); for (final WebMTrack entry : entries) { switch (entry.trackType) { From a9af1dfdd28a70b2668f201eedf25265b946adb5 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 5 Aug 2022 06:54:03 +0530 Subject: [PATCH 268/992] Applied code review changes. --- .../player/notification/NotificationConstants.java | 2 +- .../newpipe/player/notification/NotificationUtil.java | 2 +- .../settings/custom/NotificationActionsPreference.java | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java index 7015e0717..89bf0b22a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationConstants.java @@ -180,7 +180,7 @@ public final class NotificationConstants { if (compactSlot == Integer.MAX_VALUE) { // settings not yet populated, return default values - return SLOT_COMPACT_DEFAULTS; + return new ArrayList<>(SLOT_COMPACT_DEFAULTS); } // a negative value (-1) is set when the user does not want a particular compact slot diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index ef225b14e..1a91bc66d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -99,7 +99,7 @@ public final class NotificationUtil { // build the compact slot indices array (need code to convert from Integer... because Java) final List compactSlotList = NotificationConstants.getCompactSlotsFromPreferences( player.getContext(), player.getPrefs(), nonNothingSlotCount); - final int[] compactSlots = compactSlotList.stream().mapToInt(i -> i).toArray(); + final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() .setMediaSession(player.getMediaSessionManager().getSessionToken()) diff --git a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java index 1101f410d..1770685e4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java +++ b/app/src/main/java/org/schabi/newpipe/settings/custom/NotificationActionsPreference.java @@ -34,7 +34,6 @@ import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; -import java.util.ArrayList; import java.util.List; import java.util.stream.IntStream; @@ -76,9 +75,10 @@ public class NotificationActionsPreference extends Preference { //////////////////////////////////////////////////////////////////////////// private void setupActions(@NonNull final View view) { - compactSlots = new ArrayList<>(NotificationConstants - .getCompactSlotsFromPreferences(getContext(), getSharedPreferences(), 5)); - notificationSlots = IntStream.range(0, 5).mapToObj(i -> new NotificationSlot(i, view)) + compactSlots = NotificationConstants.getCompactSlotsFromPreferences(getContext(), + getSharedPreferences(), 5); + notificationSlots = IntStream.range(0, 5) + .mapToObj(i -> new NotificationSlot(i, view)) .toArray(NotificationSlot[]::new); } From ee6a27959647df5a39c57ee429046fb838457357 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 6 Aug 2022 05:09:54 +0530 Subject: [PATCH 269/992] Remove unused methods in HistoryRecordManager. --- .../org/schabi/newpipe/database/BasicDAO.java | 11 +--- .../local/history/HistoryRecordManager.java | 54 ------------------- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java b/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java index 1b8540808..255f5ba8d 100644 --- a/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/BasicDAO.java @@ -3,7 +3,6 @@ package org.schabi.newpipe.database; import androidx.room.Dao; import androidx.room.Delete; import androidx.room.Insert; -import androidx.room.OnConflictStrategy; import androidx.room.Update; import java.util.Collection; @@ -14,13 +13,10 @@ import io.reactivex.rxjava3.core.Flowable; @Dao public interface BasicDAO { /* Inserts */ - @Insert(onConflict = OnConflictStrategy.ABORT) + @Insert long insert(Entity entity); - @Insert(onConflict = OnConflictStrategy.ABORT) - List insertAll(Entity... entities); - - @Insert(onConflict = OnConflictStrategy.ABORT) + @Insert List insertAll(Collection entities); /* Searches */ @@ -32,9 +28,6 @@ public interface BasicDAO { @Delete void delete(Entity entity); - @Delete - int delete(Collection entities); - int deleteAll(); /* Updates */ diff --git a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java index 19f7afce5..b8d2eae2d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java +++ b/app/src/main/java/org/schabi/newpipe/local/history/HistoryRecordManager.java @@ -28,7 +28,6 @@ import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.AppDatabase; import org.schabi.newpipe.database.LocalItem; -import org.schabi.newpipe.database.feed.dao.FeedDAO; import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; import org.schabi.newpipe.database.history.dao.StreamHistoryDAO; import org.schabi.newpipe.database.history.model.SearchHistoryEntry; @@ -51,7 +50,6 @@ import org.schabi.newpipe.util.ExtractorHelper; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import io.reactivex.rxjava3.core.Completable; @@ -89,7 +87,6 @@ public class HistoryRecordManager { * Marks a stream item as watched such that it is hidden from the feed if watched videos are * hidden. Adds a history entry and updates the stream progress to 100%. * - * @see FeedDAO#getLiveOrNotPlayedStreams * @see FeedViewModel#togglePlayedItems * @param info the item to mark as watched * @return a Maybe containing the ID of the item if successful @@ -176,10 +173,6 @@ public class HistoryRecordManager { .subscribeOn(Schedulers.io()); } - public Flowable> getStreamHistory() { - return streamHistoryTable.getHistory().subscribeOn(Schedulers.io()); - } - public Flowable> getStreamHistorySortedById() { return streamHistoryTable.getHistorySortedById().subscribeOn(Schedulers.io()); } @@ -188,24 +181,6 @@ public class HistoryRecordManager { return streamHistoryTable.getStatistics().subscribeOn(Schedulers.io()); } - public Single> insertStreamHistory(final Collection entries) { - final List entities = new ArrayList<>(entries.size()); - for (final StreamHistoryEntry entry : entries) { - entities.add(entry.toStreamHistoryEntity()); - } - return Single.fromCallable(() -> streamHistoryTable.insertAll(entities)) - .subscribeOn(Schedulers.io()); - } - - public Single deleteStreamHistory(final Collection entries) { - final List entities = new ArrayList<>(entries.size()); - for (final StreamHistoryEntry entry : entries) { - entities.add(entry.toStreamHistoryEntity()); - } - return Single.fromCallable(() -> streamHistoryTable.delete(entities)) - .subscribeOn(Schedulers.io()); - } - private boolean isStreamHistoryEnabled() { return sharedPreferences.getBoolean(streamHistoryKey, false); } @@ -259,13 +234,6 @@ public class HistoryRecordManager { // Stream State History /////////////////////////////////////////////////////// - public Maybe getStreamHistory(final StreamInfo info) { - return Maybe.fromCallable(() -> { - final long streamId = streamTable.upsert(new StreamEntity(info)); - return streamHistoryTable.getLatestEntry(streamId); - }).subscribeOn(Schedulers.io()); - } - public Maybe loadStreamState(final PlayQueueItem queueItem) { return queueItem.getStream() .map(info -> streamTable.upsert(new StreamEntity(info))) @@ -311,28 +279,6 @@ public class HistoryRecordManager { }).subscribeOn(Schedulers.io()); } - public Single> loadStreamStateBatch(final List infos) { - return Single.fromCallable(() -> { - final List result = new ArrayList<>(infos.size()); - for (final InfoItem info : infos) { - final List entities = streamTable - .getStream(info.getServiceId(), info.getUrl()).blockingFirst(); - if (entities.isEmpty()) { - result.add(null); - continue; - } - final List states = streamStateTable - .getState(entities.get(0).getUid()).blockingFirst(); - if (states.isEmpty()) { - result.add(null); - } else { - result.add(states.get(0)); - } - } - return result; - }).subscribeOn(Schedulers.io()); - } - public Single> loadLocalStreamStateBatch( final List items) { return Single.fromCallable(() -> { From 7aacaf8c38358ccd3da98fcb26ed9420d6fdd9ee Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 29 Jul 2022 09:05:50 +0530 Subject: [PATCH 270/992] Use Collectors.joining(). --- .../org/schabi/newpipe/DownloaderImpl.java | 26 +++++++++---------- .../schabi/newpipe/error/ErrorActivity.java | 12 +++------ .../preferencesearch/PreferenceParser.java | 10 ++----- .../org/schabi/newpipe/util/CookieUtils.java | 24 ----------------- .../org/schabi/newpipe/util/Localization.java | 23 +++++----------- 5 files changed, 24 insertions(+), 71 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/util/CookieUtils.java diff --git a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java index 7bb394bf4..9ddbe96df 100644 --- a/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java +++ b/app/src/main/java/org/schabi/newpipe/DownloaderImpl.java @@ -11,15 +11,17 @@ import org.schabi.newpipe.extractor.downloader.Downloader; import org.schabi.newpipe.extractor.downloader.Request; import org.schabi.newpipe.extractor.downloader.Response; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; -import org.schabi.newpipe.util.CookieUtils; import org.schabi.newpipe.util.InfoCache; import java.io.IOException; -import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; import okhttp3.OkHttpClient; import okhttp3.RequestBody; @@ -63,19 +65,15 @@ public final class DownloaderImpl extends Downloader { } public String getCookies(final String url) { - final List resultCookies = new ArrayList<>(); - if (url.contains(YOUTUBE_DOMAIN)) { - final String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY); - if (youtubeCookie != null) { - resultCookies.add(youtubeCookie); - } - } + final String youtubeCookie = url.contains(YOUTUBE_DOMAIN) + ? getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY) : null; + // Recaptcha cookie is always added TODO: not sure if this is necessary - final String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY); - if (recaptchaCookie != null) { - resultCookies.add(recaptchaCookie); - } - return CookieUtils.concatCookies(resultCookies); + return Stream.of(youtubeCookie, getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY)) + .filter(Objects::nonNull) + .flatMap(cookies -> Arrays.stream(cookies.split("; *"))) + .distinct() + .collect(Collectors.joining("; ")); } public String getCookie(final String key) { diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java index 8b2ac37dc..e1dd929d4 100644 --- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java @@ -31,6 +31,7 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; +import java.util.stream.Collectors; /* * Created by Christian Schabesberger on 24.10.15. @@ -182,14 +183,9 @@ public class ErrorActivity extends AppCompatActivity { } private String formErrorText(final String[] el) { - final StringBuilder text = new StringBuilder(); - if (el != null) { - for (final String e : el) { - text.append("-------------------------------------\n").append(e); - } - } - text.append("-------------------------------------"); - return text.toString(); + final String separator = "-------------------------------------"; + return Arrays.stream(el) + .collect(Collectors.joining(separator + "\n", separator + "\n", separator)); } /** diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java index 1f507c7f1..b925e8b5f 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.java @@ -9,13 +9,13 @@ import androidx.annotation.Nullable; import androidx.annotation.XmlRes; import androidx.preference.PreferenceManager; +import org.schabi.newpipe.util.Localization; import org.xmlpull.v1.XmlPullParser; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; /** * Parses the corresponding preference-file(s). @@ -54,7 +54,7 @@ public class PreferenceParser { if (xpp.getEventType() == XmlPullParser.START_TAG) { final PreferenceSearchItem result = parseSearchResult( xpp, - joinBreadcrumbs(breadcrumbs), + Localization.concatenateStrings(" > ", breadcrumbs), resId ); @@ -82,12 +82,6 @@ public class PreferenceParser { return results; } - private String joinBreadcrumbs(final List breadcrumbs) { - return breadcrumbs.stream() - .filter(crumb -> !TextUtils.isEmpty(crumb)) - .collect(Collectors.joining(" > ")); - } - private String getAttribute( final XmlPullParser xpp, @NonNull final String attribute diff --git a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java b/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java deleted file mode 100644 index b906c1c4f..000000000 --- a/app/src/main/java/org/schabi/newpipe/util/CookieUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.schabi.newpipe.util; - -import android.text.TextUtils; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -public final class CookieUtils { - private CookieUtils() { - } - - public static String concatCookies(final Collection cookieStrings) { - final Set cookieSet = new HashSet<>(); - for (final String cookies : cookieStrings) { - cookieSet.addAll(splitCookies(cookies)); - } - return TextUtils.join("; ", cookieSet).trim(); - } - - public static Set splitCookies(final String cookies) { - return Set.of(cookies.split("; *")); - } -} 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 5ac24503d..28712bb4d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -32,6 +32,7 @@ import java.time.format.FormatStyle; import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; /* @@ -63,26 +64,14 @@ public final class Localization { @NonNull public static String concatenateStrings(final String... strings) { - return concatenateStrings(Arrays.asList(strings)); + return concatenateStrings(DOT_SEPARATOR, Arrays.asList(strings)); } @NonNull - public static String concatenateStrings(final List strings) { - if (strings.isEmpty()) { - return ""; - } - - final StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(strings.get(0)); - - for (int i = 1; i < strings.size(); i++) { - final String string = strings.get(i); - if (!TextUtils.isEmpty(string)) { - stringBuilder.append(DOT_SEPARATOR).append(strings.get(i)); - } - } - - return stringBuilder.toString(); + public static String concatenateStrings(final String delimiter, final List strings) { + return strings.stream() + .filter(string -> !TextUtils.isEmpty(string)) + .collect(Collectors.joining(delimiter)); } public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization( From 5c7dfd1d69bb7726b0f91cb6d5c5296f51173647 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 29 Jul 2022 09:18:21 +0530 Subject: [PATCH 271/992] Remove unused method. --- .../org/schabi/newpipe/util/Localization.java | 15 --------------- 1 file changed, 15 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 28712bb4d..e20955a76 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -347,19 +347,4 @@ public final class Localization { private static double round(final double value, final int places) { return new BigDecimal(value).setScale(places, RoundingMode.HALF_UP).doubleValue(); } - - /** - * Workaround to match normalized captions like english to English or deutsch to Deutsch. - * @param list the list to search into - * @param toFind the string to look for - * @return whether the string was found or not - */ - public static boolean containsCaseInsensitive(final List list, final String toFind) { - for (final String i : list) { - if (i.equalsIgnoreCase(toFind)) { - return true; - } - } - return false; - } } From ebd06bdd246403384f6d93795d102bc5d22cfce0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 6 Aug 2022 11:56:00 +0200 Subject: [PATCH 272/992] Improve comment --- app/src/main/java/org/schabi/newpipe/util/ListHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java index 45234706b..b3b7c1792 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ListHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ListHelper.java @@ -377,7 +377,7 @@ public final class ListHelper { static int getMostCompactAudioIndex(@Nullable final MediaFormat format, @Nullable final List audioStreams) { return getAudioIndexByHighestRank(format, audioStreams, - // The "-" is important -> Compares ascending (first = highest rank) + // The "reversed()" is important -> Compares ascending (first = highest rank) getAudioStreamComparator(AUDIO_FORMAT_EFFICIENCY_RANKING).reversed()); } From e136a6f91538bf3a7f4710ab1b9f206a55835b90 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 8 Aug 2022 07:10:16 +0530 Subject: [PATCH 273/992] Use range-limiting methods in more places. --- .../org/schabi/newpipe/local/feed/FeedFragment.kt | 3 ++- .../player/gesture/MainPlayerGestureListener.kt | 5 ++--- .../schabi/newpipe/player/ui/PopupPlayerUi.java | 2 +- .../org/schabi/newpipe/util/ReleaseVersionUtil.kt | 14 ++++++-------- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 899163050..c76e69e2a 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -40,6 +40,7 @@ import androidx.annotation.Nullable import androidx.appcompat.app.AlertDialog import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.edit +import androidx.core.math.MathUtils import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider @@ -584,7 +585,7 @@ class FeedFragment : BaseStateFragment() { // state until the user scrolls them out of the visible area which causes a update/bind-call groupAdapter.notifyItemRangeChanged( 0, - minOf(groupAdapter.itemCount, maxOf(highlightCount, lastNewItemsCount)) + MathUtils.clamp(highlightCount, lastNewItemsCount, groupAdapter.itemCount) ) if (highlightCount > 0) { diff --git a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt index 095b3ccdb..a6dba0dd5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt +++ b/app/src/main/java/org/schabi/newpipe/player/gesture/MainPlayerGestureListener.kt @@ -7,6 +7,7 @@ import android.view.View.OnTouchListener import android.widget.ProgressBar import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources +import androidx.core.math.MathUtils import androidx.core.view.isVisible import org.schabi.newpipe.MainActivity import org.schabi.newpipe.R @@ -18,8 +19,6 @@ import org.schabi.newpipe.player.helper.PlayerHelper import org.schabi.newpipe.player.ui.MainPlayerUi import org.schabi.newpipe.util.ThemeHelper.getAndroidDimenPx import kotlin.math.abs -import kotlin.math.max -import kotlin.math.min /** * GestureListener for the player @@ -114,7 +113,7 @@ class MainPlayerGestureListener( // Update progress bar val oldBrightness = layoutParams.screenBrightness - bar.progress = (bar.max * max(0f, min(1f, oldBrightness))).toInt() + bar.progress = (bar.max * MathUtils.clamp(oldBrightness, 0f, 1f)).toInt() bar.incrementProgressBy(distanceY.toInt()) // Update brightness 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 aa36a6a5a..90c24c0c6 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 @@ -291,7 +291,7 @@ public final class PopupPlayerUi extends VideoPlayerUi { } final float minimumWidth = context.getResources().getDimension(R.dimen.popup_minimum_width); - final int actualWidth = Math.min((int) Math.max(width, minimumWidth), screenWidth); + final int actualWidth = MathUtils.clamp(width, (int) minimumWidth, screenWidth); final int actualHeight = (int) getMinimumVideoHeight(width); if (DEBUG) { Log.d(TAG, "updatePopupSize() updated values:" diff --git a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt index 0c66cc6d4..5a54b29d2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt +++ b/app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt @@ -100,13 +100,11 @@ object ReleaseVersionUtil { * @return Epoch second of expiry date time */ fun coerceUpdateCheckExpiry(expiryString: String?): Long { - val now = ZonedDateTime.now() - return expiryString?.let { - var expiry = - ZonedDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(expiryString)) - expiry = maxOf(expiry, now.plusHours(6)) - expiry = minOf(expiry, now.plusHours(72)) - expiry.toEpochSecond() - } ?: now.plusHours(6).toEpochSecond() + val nowPlus6Hours = ZonedDateTime.now().plusHours(6) + val expiry = expiryString?.let { + ZonedDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(it)) + .coerceIn(nowPlus6Hours, nowPlus6Hours.plusHours(66)) + } ?: nowPlus6Hours + return expiry.toEpochSecond() } } From 0b11afaf2fddff26de7927605994dea1c4be5b87 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Mon, 8 Aug 2022 19:32:21 -0400 Subject: [PATCH 274/992] Update Gradle to 7.5.1 --- gradle/wrapper/gradle-wrapper.jar | Bin 59821 -> 60756 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 6 ++++++ gradlew.bat | 14 ++++++++------ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a4d4fb3f96a785543079b8df6723c946b..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 10197 zcmaKS1ymhDwk=#NxVyW%y9U<)A-Dv)xI0|j{UX8L-JRg>5ZnnKAh;%chM6~S-g^K4 z>eZ{yK4;gd>gwvXs=Id8Jk-J}R4pT911;+{Jp9@aiz6!p1Oz9z&_kGLA%J5%3Ih@0 zQ|U}%$)3u|G`jIfPzMVfcWs?jV2BO^*3+q2><~>3j+Z`^Z%=;19VWg0XndJ zwJ~;f4$;t6pBKaWn}UNO-wLCFHBd^1)^v%$P)fJk1PbK5<;Z1K&>k~MUod6d%@Bq9 z>(44uiaK&sdhwTTxFJvC$JDnl;f}*Q-^01T508(8{+!WyquuyB7R!d!J)8Ni0p!cV6$CHsLLy6}7C zYv_$eD;)@L)tLj0GkGpBoa727hs%wH$>EhfuFy{_8Q8@1HI%ZAjlpX$ob{=%g6`Ox zLzM!d^zy`VV1dT9U9(^}YvlTO9Bf8v^wMK37`4wFNFzW?HWDY(U(k6@tp(crHD)X5>8S-# zW1qgdaZa*Sh6i%60e1+hty}34dD%vKgb?QmQiZ=-j+isA4={V_*R$oGN#j|#ia@n6 zuZx4e2Xx?^lUwYFn2&Tmbx0qA3Z8;y+zKoeQu;~k~FZGy!FU_TFxYd!Ck;5QvMx9gj5fI2@BLNp~Ps@ zf@k<&Q2GS5Ia9?_D?v~$I%_CLA4x~eiKIZ>9w^c#r|vB?wXxZ(vXd*vH(Fd%Me8p( z=_0)k=iRh%8i`FYRF>E97uOFTBfajv{IOz(7CU zv0Gd84+o&ciHlVtY)wn6yhZTQQO*4Mvc#dxa>h}82mEKKy7arOqU$enb9sgh#E=Lq zU;_RVm{)30{bw+|056%jMVcZRGEBSJ+JZ@jH#~DvaDQm92^TyUq=bY*+AkEakpK>8 zB{)CkK48&nE5AzTqT;WysOG|!y}5fshxR8Ek(^H6i>|Fd&wu?c&Q@N9ZrJ=?ABHI! z`*z8D`w=~AJ!P-9M=T}f`;76$qZRllB&8#9WgbuO$P7lVqdX1=g*t=7z6!0AQ^ux_ z9rcfUv^t}o_l-ZE+TqvqFsA*~W<^78!k;~!i8(eS+(+@u8FxK+Q7;mHZ<1}|4m<}vh@p`t%|@eM_J(P% zI>M7C)Ir{l|J;$G_EGGEhbP4?6{sYzMqBv+x95N&YWFH6UcE@b}B?q)G*4<4mR@sy1#vPnLMK51tb#ED(8TA1nE zYfhK7bo1!R5WJF$5Y?zG21)6+_(_5oSX9sGIW;(O&S?Rh(nydNQYzKjjJ54aDJ-1F zrJ=np8LsN?%?Rt7f~3aAX!2E{`fh_pb?2(;HOB3W+I*~A>W%iY+v45+^e$cE10fA} zXPvw9=Bd+(;+!rl)pkYj0HGB}+3Z!Mr;zr%gz~c-hFMv8b2VRE2R$8V=_XE zq$3=|Yg05(fmwrJ)QK2ptB4no`Y8Dg_vK2QDc6-6sXRQ5k78-+cPi-fH}vpgs|Ive zE=m*XNVs?EWgiNI!5AcD*3QMW)R`EqT!f0e1%hERO&?AT7HWnSf5@#AR{OGuXG3Zb zCnVWg7h|61lGV3k+>L<#d>)InG>ETn1DbOHCfztqzQ_fBiaUt@q6VMy={Fe-w#~2- z0?*f|z$zgjI9>+JVICObBaK=pU}AEOd@q(8d?j7zQFD@=6t`|KmolTr2MfBI$;EGh zD%W0cA_d#V6Lb$us5yIG(|d>r-QleC4;%hEu5W9hyY zY#+ESY&v`8(&mC~?*|e5WEhC!YU2>m_}`K+q9)a(d$bsS<=YkyZGp}YA%TXw>@abA zS_poVPoN+?<6?DAuCNt&5SHV(hp56PJ})swwVFZFXM->F zc|0c8<$H_OV%DR|y7e+s$12@Ac8SUClPg8_O9sTUjpv%6Jsn5vsZCg>wL+db4c+{+ zsg<#wOuV4jeOq`veckdi-1`dz;gvL)bZeH|D*x=8UwRU5&8W1@l>3$)8WzET0%;1J zM3(X<7tKK&9~kWRI{&FmwY5Gg!b5f4kI_vSm)H1#>l6M+OiReDXC{kPy!`%Ecq-+3yZTk=<` zm)pE6xum5q0Qkd#iny0Q-S}@I0;mDhxf>sX)Oiv)FdsAMnpx%oe8OQ`m%Xeozdzx!C1rQR>m1c_}+J4x)K}k{G zo68;oGG&Ox7w^-m7{g4a7NJu-B|~M;oIH~~#`RyUNm##feZH;E?pf}nshmoiIY52n z%pc%lnU4Q#C=RUz)RU6}E_j4#)jh<&a%JyJj$Fufc#&COaxFHtl}zJUGNLBu3~_@1 zn9F^JO9);Duxo&i@>X(kbYga1i>6p1fca8FzQ0>((Lb-aPUbC*d~a03V$y;*RBY!R ziEJ2IF^FjrvO}0Uy{cMn%u<+P5U!UO>pm9#ZYL5i6|xSC+np7IH$GfXs&uI;y4as@ z&AzJh>(S2?3PKKgab3Z(`xbx(C#46XIvVcW8eG_DjT~}Yz_8PWZ`uf6^Xr=vkvL_` zqmvfgJL+Zc`;iq~iP?%@G7}~fal-zqxa0yNyHBJJ5M)9bI>7S_cg?Ya&p(I)C5Ef4 zZ>YAF6x|U=?ec?g*|f2g5Tw3PgxaM_bi_5Az9MO$;_Byw(2d}2%-|bg4ShdQ;)Z|M z4K|tFv)qx*kKGKoyh!DQY<{n&UmAChq@DJrQP>EY7g1JF(ih*D8wCVWyQ z5Jj^|-NVFSh5T0vd1>hUvPV6?=`90^_)t(L9)XOW7jeP45NyA2lzOn&QAPTl&d#6P zSv%36uaN(9i9WlpcH#}rmiP#=L0q(dfhdxvFVaOwM;pY;KvNQ9wMyUKs6{d}29DZQ z{H3&Sosr6)9Z+C>Q5)iHSW~gGoWGgK-0;k~&dyr-bA3O|3PCNzgC?UKS_B=^i8Ri^ zd_*_qI4B07Cayq|p4{`U_E_P=K`N_~{F|+-+`sCgcNxs`%X!$=(?l2aAW}0M=~COb zf19oe^iuAUuDEf)4tgv<=WRPpK@IjToNNC*#&Ykw!)aqWU4h#|U@(cG_=Qx+&xt~a zvCz~Ds3F71dsjNLkfM%TqdVNu=RNMOzh7?b+%hICbFlOAPphrYy>7D-e7{%o_kPFn z;T!?ilE-LcKM0P(GKMseEeW57Vs`=FF}(y@^pQl;rL3fHs8icmA+!6YJt&8 ztSF?%Un35qkv>drkks&BNTJv~xK?vD;aBkp7eIkDYqn+G0%;sT4FcwAoO+vke{8CO z0d76sgg$CannW5T#q`z~L4id)9BCKRU0A!Z-{HpXr)QJrd9@iJB+l32Ql)Z}*v(St zE)Vp=BB=DDB4Pr}B(UHNe31<@!6d{U?XDoxJ@S)9QM)2L%SA0x^~^fb=bdsBy!uh& zU?M_^kvnt%FZzm+>~bEH{2o?v&Iogs`1t-b+Ml`J!ZPS(46YQJKxWE81O$HE5w;** z|8zM%bp`M7J8)4;%DqH`wVTmM0V@D}xd%tRE3_6>ioMJxyi5Hkb>85muF81&EY!73ei zA3e<#ug||EZJ=1GLXNJ)A z791&ge#lF;GVX6IU?iw0jX^1bYaU?+x{zPlpyX6zijyn*nEdZ$fxxkl!a-~*P3bkf zPd*pzu~3GBYkR_>ET`5UM^>>zTV>5m>)f=az{d0sg6a8VzUtXy$ZS?h#Gk-CA?7)c zI%Vu9DN6XSDQn6;?n9`>l$q&>s?K)R8*OsmI+$L_m z_~E`}w694Z*`Xk3Ne=497Si~=RWRqCM?6=88smrxle#s*W znwhTRsMRmg?37GLJ-)%nDZA7r$YG849j8mJWir1bWBy& zZPneYojSbooC8U@tkO`bWx4%E5*;p#Q^1^S3lsfy7(6A{jL0`A__0vm?>xC%1y8_m z57FfWr^@YG2I1K7MGYuYd>JC}@sT2n^rkrY3w%~$J$Y~HSoOHn?zpR$ zjLj_bq@Yj8kd~DXHh30KVbz@K)0S;hPKm+S&-o%IG+@x@MEcrxW2KFh;z^4dJDZix zGRGe&lQD$p)0JVF4NRgGYuh0bYLy)BCy~sbS3^b3 zHixT<%-Vwbht|25T{3^Hk;qZ^3s!OOgljHs+EIf~C%=_>R5%vQI4mQR9qOXThMXlU zS|oSH>0PjnCakb*js2{ObN`}%HYsT6=%(xA| znpUtG_TJ08kHgm5l@G|t?4E3tG2fq?wNtIp*Vqrb{9@bo^~Rx7+J&OnayrX`LDcF~ zd@0m0ZJ#Z@=T>4kTa5e2FjI&5c(F7S{gnRPoGpu9eIqrtSvnT_tk$8T)r%YwZw!gK zj*k@cG)V&@t+mtDi37#>LhVGTfRA^p%x0d#_P|Mktz3*KOoLIqFm`~KGoDDD4OOxe z?}ag_c08u%vu=5Vx=~uoS8Q;}+R2~?Uh|m-+`-2kDo$d6T!nD*hc#dB(*R{LXV=zo z`PJP0V=O!@3l-bw+d`X6(=@fq=4O#ETa8M^fOvO4qja9o3e8ANc9$sI=A4$zUut~w z4+JryRkI{9qWxU1CCMM$@Aj=6)P+z?vqa=UCv_4XyVNoBD{Xb~Oi4cjjhm8fRD!*U z2)zaS;AI78^Wq+5mDInKiMz|z#K`2emQfNH*U;{9^{NqSMVoq?RSo43<8YpJM^+W$ zxy!A5>5Zl16Vi#?nAYywu3w_=KWnd3*QetocWt`3pK67>)ZVwnT3h zbPdD&MZkD?q=-N`MpCCwpM74L+Tr1aa)zJ)8G;(Pg51@U&5W>aNu9rA`bh{vgfE={ zdJ>aKc|2Ayw_bop+dK?Y5$q--WM*+$9&3Q9BBiwU8L<-`T6E?ZC`mT0b}%HR*LPK} z!MCd_Azd{36?Y_>yN{U1w5yrN8q`z(Vh^RnEF+;4b|2+~lfAvPT!`*{MPiDioiix8 zY*GdCwJ{S(5(HId*I%8XF=pHFz<9tAe;!D5$Z(iN#jzSql4sqX5!7Y?q4_%$lH zz8ehZuyl0K=E&gYhlfFWabnSiGty$>md|PpU1VfaC5~kskDnZX&Yu}?-h;OSav=8u z=e3Yq=mi$4A|sB-J00;1d{Sd1+!v0NtU((Nz2;PFFlC}V{@p&4wGcVhU&nI($RAS! zwXn7)?8~1J3*4+VccRSg5JS<(bBhBM&{ELMD4C_NTpvzboH!{Zr*%HP;{UqxI#g&7 zOAqPSW5Qus$8-xtTvD%h{Tw<2!XR(lU54LZG{)Cah*LZbpJkA=PMawg!O>X@&%+5XiyeIf91n2E*hl$k-Y(3iW*E}Mz-h~H~7S9I1I zR#-j`|Hk?$MqFhE4C@=n!hN*o5+M%NxRqP+aLxDdt=wS6rAu6ECK*;AB%Nyg0uyAv zO^DnbVZZo*|Ef{nsYN>cjZC$OHzR_*g%T#oF zCky9HJS;NCi=7(07tQXq?V8I&OA&kPlJ_dfSRdL2bRUt;tA3yKZRMHMXH&#W@$l%-{vQd7y@~i*^qnj^`Z{)V$6@l&!qP_y zg2oOd!Wit#)2A~w-eqw3*Mbe)U?N|q6sXw~E~&$!!@QYX4b@%;3=>)@Z#K^`8~Aki z+LYKJu~Y$;F5%_0aF9$MsbGS9Bz2~VUG@i@3Fi2q(hG^+Ia44LrfSfqtg$4{%qBDM z_9-O#3V+2~W$dW0G)R7l_R_vw(KSkC--u&%Rs^Io&*?R=`)6BN64>6>)`TxyT_(Rd zUn+aIl1mPa#Jse9B3`!T=|e!pIp$(8ZOe0ao?nS7o?oKlj zypC-fMj1DHIDrh1unUI1vp=-Fln;I9e7Jvs3wj*^_1&W|X} zZSL|S|Bb@CV*YC_-T&2!Ht3b6?)d`tHOP?rA;;t#zaXa0Sc;vGnV0BLIf8f-r{QHh z*Zp`4_ItlOR7{u(K+!p_oLDmaAkNag*l4#29F2b_A*0oz0T|#-&f*;c#<`^)(W@gm z#k9k=t%u8<+C1fNUA{Fh7~wgPrEZZ#(6aBI%6bR4RO(e1(ZocjoDek4#MTgZD>1NG zy9~yoZfWYfwe&S-(zk4o6q6o?2*~DOrJ(%5wSnEJMVOKCzHd z=Yhm+HLzoDl{P*Ybro7@sk1!Ez3`hE+&qr7Rw^2glw^M(b(NS2!F|Q!mi|l~lF94o z!QiV)Q{Z>GO5;l1y!$O)=)got;^)%@v#B!ZEVQy1(BJApHr5%Zh&W|gweD+%Ky%CO ztr45vR*y(@*Dg_Qw5v~PJtm^@Lyh*zRuT6~(K+^HWEF{;R#L$vL2!_ndBxCtUvZ(_ zauI7Qq}ERUWjr&XW9SwMbU>*@p)(cuWXCxRK&?ZoOy>2VESII53iPDP64S1pl{NsC zD;@EGPxs&}$W1;P6BB9THF%xfoLX|4?S;cu@$)9OdFst-!A7T{(LXtdNQSx!*GUSIS_lyI`da8>!y_tpJb3Zuf0O*;2y?HCfH z5QT6@nL|%l3&u4;F!~XG9E%1YwF*Fgs5V&uFsx52*iag(?6O|gYCBY3R{qhxT-Etb zq(E%V=MgQnuDGEKOGsmBj9T0-nmI%zys8NSO>gfJT4bP>tI>|ol@ zDt(&SUKrg%cz>AmqtJKEMUM;f47FEOFc%Bbmh~|*#E zDd!Tl(wa)ZZIFwe^*)4>{T+zuRykc3^-=P1aI%0Mh}*x7%SP6wD{_? zisraq`Las#y-6{`y@CU3Ta$tOl|@>4qXcB;1bb)oH9kD6 zKym@d$ zv&PZSSAV1Gwwzqrc?^_1+-ZGY+3_7~a(L+`-WdcJMo>EWZN3%z4y6JyF4NR^urk`c z?osO|J#V}k_6*9*n2?j+`F{B<%?9cdTQyVNm8D}H~T}?HOCXt%r7#2hz97Gx#X%62hyaLbU z_ZepP0<`<;eABrHrJAc!_m?kmu#7j}{empH@iUIEk^jk}^EFwO)vd7NZB=&uk6JG^ zC>xad8X$h|eCAOX&MaX<$tA1~r|hW?-0{t4PkVygTc`yh39c;&efwY(-#;$W)+4Xb z$XFsdG&;@^X`aynAMxsq)J#KZXX!sI@g~YiJdHI~r z$4mj_?S29sIa4c$z)19JmJ;Uj?>Kq=0XuH#k#};I&-6zZ_&>)j>UR0XetRO!-sjF< zd_6b1A2vfi++?>cf}s{@#BvTD|a%{9si7G}T+8ZnwuA z1k8c%lgE<-7f~H`cqgF;qZ|$>R-xNPA$25N1WI3#n%gj}4Ix}vj|e=x)B^roGQpB) zO+^#nO2 zjzJ9kHI6nI5ni&V_#5> z!?<7Qd9{|xwIf4b0bRc;zb}V4>snRg6*wl$Xz`hRDN8laL5tg&+@Dv>U^IjGQ}*=XBnXWrwTy;2nX?<1rkvOs#u(#qJ=A zBy>W`N!?%@Ay=upXFI}%LS9bjw?$h)7Dry0%d}=v0YcCSXf9nnp0tBKT1eqZ-4LU` zyiXglKRX)gtT0VbX1}w0f2ce8{$WH?BQm@$`ua%YP8G@<$n13D#*(Yd5-bHfI8!on zf5q4CPdgJLl;BqIo#>CIkX)G;rh|bzGuz1N%rr+5seP${mEg$;uQ3jC$;TsR&{IX< z;}7j3LnV+xNn^$F1;QarDf6rNYj7He+VsjJk6R@0MAkcwrsq4?(~`GKy|mgkfkd1msc2>%B!HpZ~HOzj}kl|ZF(IqB=D6ZTVcKe=I7)LlAI=!XU?J*i#9VXeKeaG zwx_l@Z(w`)5Cclw`6kQKlS<;_Knj)^Dh2pL`hQo!=GPOMR0iqEtx12ORLpN(KBOm5 zontAH5X5!9WHS_=tJfbACz@Dnkuw|^7t=l&x8yb2a~q|aqE_W&0M|tI7@ilGXqE)MONI8p67OiQGqKEQWw;LGga=ZM1;{pSw1jJK_y$vhY6 ztFrV7-xf>lbeKH1U)j3R=?w*>(Yh~NNEPVmeQ8n}0x01$-o z2Jyjn+sXhgOz>AzcZ zAbJZ@f}MBS0lLKR=IE{z;Fav%tcb+`Yi*!`HTDPqSCsFr>;yt^^&SI2mhKJ8f*%ji zz%JkZGvOn{JFn;)5jf^21AvO-9nRzsg0&CPz;OEn07`CfT@gK4abFBT$Mu?8fCcscmRkK+ zbAVJZ~#_a z{|(FFX}~8d3;DW8zuY9?r#Dt>!aD>} zlYw>D7y#eDy+PLZ&XKIY&Df0hsLDDi(Yrq8O==d30RchrUw8a=Eex>Dd?)3+k=}Q> z-b85lun-V$I}86Vg#l1S@1%=$2BQD5_waAZKQfJ${3{b2SZ#w1u+jMr{dJMvI|Og= zpQ9D={XK|ggbe04zTUd}iF{`GO1dV%zWK~?sM9OM(= zVK9&y4F^w1WFW{$qi|xQk0F`@HG8oLI5|5$j~ci9xTMT69v5KS-Yym--raU5kn2#C z<~5q^Bf0rTXVhctG2%&MG(cUGaz(gC(rcG~>qgO$W6>!#NOVQJ;pIYe-lLy(S=HgI zPh;lkL$l+FfMHItHnw_^bj8}CKM19t(C_2vSrhX2$K@-gFlH};#C?1;kk&U1L%4S~ zR^h%h+O1WE7DI$~dly?-_C7>(!E`~#REJ~Xa7lyrB$T!`&qYV5QreAa^aKr%toUJR zPWh)J3iD`(P6BI5k$oE$us#%!4$>`iH2p-88?WV0M$-K)JDibvA4 zpef%_*txN$Ei3=Lt(BBxZ&mhl|mUz-z*OD1=r9nfN zc5vOMFWpi>K=!$6f{eb?5Ru4M3o;t9xLpry|C%j~`@$f)OFB5+xo8XM8g&US@UU-sB|dAoc20y(F@=-2Ggp_`SWjEb#>IG^@j zuQK}e^>So#W2%|-)~K!+)wdU#6l>w5wnZt2pRL5Dz#~N`*UyC9tYechBTc2`@(OI# zNvcE*+zZZjU-H`QOITK^tZwOyLo)ZCLk>>Wm+flMsr5X{A<|m`Y281n?8H_2Fkz5}X?i%Rfm5s+n`J zDB&->=U+LtOIJ|jdYXjQWSQZFEs>Rm{`knop4Sq)(}O_@gk{14y51)iOcGQ5J=b#e z2Yx^6^*F^F7q_m-AGFFgx5uqyw6_4w?yKCJKDGGprWyekr;X(!4CnM5_5?KgN=3qCm03 z##6k%kIU5%g!cCL(+aK>`Wd;dZ4h$h_jb7n?nqx5&o9cUJfr%h#m4+Bh)>HodKcDcsXDXwzJ3jR(sSFqWV(OKHC*cV8;;&bH=ZI0YbW3PgIHwTjiWy z?2MXWO2u0RAEEq(zv9e%Rsz|0(OKB?_3*kkXwHxEuazIZ7=JhaNV*P~hv57q55LoebmJpfHXA@yuS{Esg+ z*C}0V-`x^=0nOa@SPUJek>td~tJ{U1T&m)~`FLp*4DF77S^{|0g%|JIqd-=5)p6a` zpJOsEkKT(FPS@t^80V!I-YJbLE@{5KmVXjEq{QbCnir%}3 zB)-J379=wrBNK6rbUL7Mh^tVmQYn-BJJP=n?P&m-7)P#OZjQoK0{5?}XqJScV6>QX zPR>G{xvU_P;q!;S9Y7*07=Z!=wxIUorMQP(m?te~6&Z0PXQ@I=EYhD*XomZ^z;`Os z4>Uh4)Cg2_##mUa>i1Dxi+R~g#!!i{?SMj%9rfaBPlWj_Yk)lCV--e^&3INB>I?lu z9YXCY5(9U`3o?w2Xa5ErMbl5+pDVpu8v+KJzI9{KFk1H?(1`_W>Cu903Hg81vEX32l{nP2vROa1Fi!Wou0+ZX7Rp`g;B$*Ni3MC-vZ`f zFTi7}c+D)!4hz6NH2e%%t_;tkA0nfkmhLtRW%){TpIqD_ev>}#mVc)<$-1GKO_oK8 zy$CF^aV#x7>F4-J;P@tqWKG0|D1+7h+{ZHU5OVjh>#aa8+V;6BQ)8L5k9t`>)>7zr zfIlv77^`Fvm<)_+^z@ac%D&hnlUAFt8!x=jdaUo{)M9Ar;Tz5Dcd_|~Hl6CaRnK3R zYn${wZe8_BZ0l0c%qbP}>($jsNDay>8+JG@F!uV4F;#zGsBP0f$f3HqEHDz_sCr^q z1;1}7KJ9&`AX2Qdav1(nNzz+GPdEk5K3;hGXe{Hq13{)c zZy%fFEEH#nlJoG{f*M^#8yXuW%!9svN8ry-Vi7AOFnN~r&D`%6d#lvMXBgZkX^vFj z;tkent^62jUr$Cc^@y31Lka6hS>F?1tE8JW$iXO*n9CQMk}D*At3U(-W1E~z>tG?> z5f`5R5LbrhRNR8kv&5d9SL7ke2a*Xr)Qp#75 z6?-p035n2<7hK;sb>t9GAwG4{9v~iEIG>}7B5zcCgZhu$M0-z8?eUO^E?g)md^XT_ z2^~-u$yak>LBy(=*GsTj6p<>b5PO&un@5hGCxpBQlOB3DpsItKZRC*oXq-r{u}Wb; z&ko>#fbnl2Z;o@KqS-d6DTeCG?m1 z&E>p}SEc*)SD&QjZbs!Csjx~0+$@ekuzV_wAalnQvX3a^n~3ui)|rDO+9HW|JPEeBGP4 z)?zcZ<8qv47`EWA*_X~H^vr(lP|f%=%cWFM;u)OFHruKT<~?>5Y8l?56>&;=WdZU# zZEK4-C8s-3zPMA^&y~e*9z)!ZJghr3N^pJa2A$??Xqx-BR*TytGYor&l8Q+^^r%Yq02xay^f#;;wO6K7G!v>wRd6531WnDI~h$PN( z+4#08uX?r&zVKsQ;?5eBX=FxsXaGyH4Gth4a&L|{8LnNCHFr1M{KjJ!BfBS_aiy-E zxtmNcXq3}WTwQ7Dq-9YS5o758sT(5b`Sg-NcH>M9OH1oW6&sZ@|GYk|cJI`vm zO<$~q!3_$&GfWetudRc*mp8)M)q7DEY-#@8w=ItkApfq3sa)*GRqofuL7)dafznKf zLuembr#8gm*lIqKH)KMxSDqbik*B(1bFt%3Vv|ypehXLCa&wc7#u!cJNlUfWs8iQ` z$66(F=1fkxwg745-8_eqV>nWGY3DjB9gE23$R5g&w|C{|xvT@7j*@aZNB199scGchI7pINb5iyqYn)O=yJJX)Ca3&Ca+{n<=1w|(|f0)h<9gs$pVSV<<9Og-V z8ki@nKwE)x)^wmHBMk?mpMT=g{S#^8W|>&rI#Ceh;9za}io0k@0JxiCqi-jHlxbt3 zjJA?RihhRvhk6%G5-D{ePh1jare*fQS<328P-DcVAxPTrw=n6k?C6EV75f}cnBRPT zMYDqqKu(ND&aOtc!QRV`vzJSVxx8i~WB#5Ml{b#eQqNnSi7l-bS-`ITW<^zyYQA(b zbj4SuRK>q9o`_v%+C=S?h>2e4!66Ij(P5{7Uz$3u6YJJC$W%EoBa{-(=tQ|y1vov%ZkXVOV z##_UVg4V^4ne#4~<-1DkJqkKqgT+E_=&4Ue&eQ-JC+gi?7G@d6= zximz{zE)WW{b@QCJ!7l&N5x=dXS?$5RBU-VvN4Uec-GHK&jPa&P2z+qDdLhIB+HU) zu0CW&uLvE^4I5xtK-$+oe|58)7m6*PO%Xt<+-XEA%jG_BEachkF3e@pn?tl!`8lOF zbi2QOuNXX)YT*MCYflILO{VZ*9GiC%R4FO20zMK?p+&aCMm2oeMK7(aW=UDzr=AO0 z$5mJ%=qRsR8rZ>_YsL+vi{3*J_9Kzq(;ZwRj+4_f0-*wbkSMPWahX#Fj_a8BnrhJ6 zo^ZZ?Vah1@&6#r=JkuaYDBdp;J3@ii+CHM&@9*er&#P}$@wI$bfrH)&c!*|nkvhf%^*Y6b%dKz%QBSIo@U z{?V^qEs4`q<8@n+u8YiB^sc@6g>TncG<|GsmC3egwE6aO=EwLr~3-2 zNr`+)`i+-83?|1Xy0^8ps&pb}YT?w1eWVnC9Ps1=KM;Rw)bH6O!7Did1NwpnqVPZc z*%Qo~qkDL>@^<^fmIBtx$WUWQiNtAB2x-LO^BB=|w~-zTnJNEdm1Ou(?8PF&U88X@ z#8rdaTd||)dG^uJw~N_-%!XNbuAyh4`>Shea=pSj0TqP+w4!`nxsmVSv02kb`DBr% zyX=e>5IJ3JYPtdbCHvKMdhXUO_*E9jc_?se7%VJF#&ZaBD;7+eFN3x+hER7!u&`Wz z7zMvBPR4y`*$a250KYjFhAKS%*XG&c;R-kS0wNY1=836wL6q02mqx;IPcH(6ThA@2 zXKQF|9H>6AW$KUF#^A%l6y5{fel77_+cR_zZ0(7=6bmNXABv}R!B-{(E^O6Y?ZS)n zs1QEmh_Fm7p}oRyT3zxUNr4UV8NGs+2b8|4shO$OGFj3D&7_e?#yDi=TTe%$2QbG5 zk<;q7aQ;p!M-Osm{vFdmXZ@!z9uWh!;*%>(vTRggufuUGP9Hols@vhx z73pn$3u2;vzRvnXuT&$Os7J@6y12*j!{ix%3B4YU1466ItmJs0NsU(4ZYRYh7wEA6q{b*Hs6@k~ zi7Yq@Ax!et0cUMTvk7P%ym){MHpcliHEI~e3HP0NV=}7;xFv#IC?a<=`>~j_sk{e> z7vg-tK*p83HZ0=QK@ zRIHo^r{D8&Ms-^WZp+6US_Quqjh$Q66W^1}=Uz&XJ8AQE9&2}P zY|FXZzZ|0IiaBd2qdt6dIjQr(ZMIOU%NG1F&fu6Po9m^?BvLhI6T0R!H2d8;U(&p2 zYA|MFscMqcO(ye~Jp?F;0>Ke+5hzVr?aBNe>GsGgr$XrpS9uajN2kNQ3o$V5rp0T( z0$6TJC;3)26SNG#XcX7l^MKTn$ga?6r4Jzfb%ZgA(Zbwit0$kY=avSnI$@Gk%+^pu zS5mHrcRS8LFPC*uVWH4DDD1pY$H8N>X?KIJZuZ2SvTqc5Nr0GHdD8TCJcd$zIhOdC zZX0ErnsozQh;t^==4zTfrZO421AL?)O)l#GSxU#|LTTg4#&yeK=^w#;q63!Nv~1(@ zs^-RNRuF&qgcr+bIzc@7$h9L;_yjdifE*$j0Q&Np=1AuHL--zdkv@}`1 zo~LlDl_YAq*z?vmr4M`GjDkl9?p|-tl(DtX76oZv25_DtZutLS9Ez!5~p?th@4 zyc_uax4W#<(#)LMkvo)yp|5tKsC2=p#6PyhpH|449T<9Zdk|%CAb5cw?fhvQtBO&7 zpQ9$24yLqPHP;$N&fe2wm%8qdctwIna<3SwGtQA3{C77s%CW%LYxtK(SBGustL0<( zu~U9r0UOkr(c{OJxZS0Ntu3+cJlF7R`7k-Bsa&q?9Ae5{{|o~?cM+T7{lB1^#vT8R z?>c9fNWey`1dKDY%F3d2O*8^qYhjlB8*7HMKE<*=(A`{>=1%s1}Pm&#_t1xy!FkPk@%SMEka2@*= zxDuM|vJJ5s+xgDls{>*o!7eOcs|xuVBPWX&+y5vEiADK%hi`#Dbd>;;Pbk2H4*-X&R?_-6ZEutSd8hC+sSjhIo z;D(j4P;2EVpEj#UF7IjM6PC+X$C5T&=nL`*!*hm9U)#O?>wqOgC>jXKN3Slk_yaQX zLf|4D8T4k|wHW`;#ZQVocNF|3izi0sOqXzi7@KlYC3CXBG`94wD;tMI1bj|8Vm zY}9`VI9!plSfhAal$M_HlaYOVNU?9Z#0<$o?lXXbX3O(l_?f)i3_~r+GcO-x#+x^X zfsZl0>Rj2iP1rsT;+b;Mr? z4Vu&O)Q5ru4j;qaSP5gA{az@XTS1NpT0d9Xhl_FkkRpcEGA0(QQ~YMh#&zwDUkNzm z6cgkdgl9W{iL6ArJ1TQHqnQ^SQ1WGu?FT|93$Ba}mPCH~!$3}0Y0g zcoG%bdTd$bmBx9Y<`Jc+=Cp4}c@EUfjiz;Rcz101p z=?#i$wo>gBE9|szaZMt-d4nUIhBnYRuBVyx+p?5#aZQgUe(!ah`J#l1$%bl5avL27 zU2~@V`3Ic&!?FhDX@Cw!R4%xtWark#p8DLT)HCZ?VJxf^yr@AD*!ERK3#L$E^*Yr? zzN&uF9Roh4rP+r`Z#7U$tzl6>k!b~HgM$C<_crP=vC>6=q{j?(I}!9>g3rJU(&){o z`R^E*9%+kEa8H_fkD9VT7(Fks&Y-RcHaUJYf-|B+eMXMaRM;{FKRiTB>1(=Iij4k1(X__|WqAd-~t#2@UQ}Z&<1Th0azdXfoll!dd)6>1miA z!&=6sDJm=e$?L&06+Q3`D-HNSkK-3$3DdZMX-6Xjn;wd#9A{~ur!2NcX>(qY_oZL0~H7dnQ9sgLe!W>~2|RSW7|hWn<({Pg*xF$%B-!rKe^_R_vc z(LO!0agxxP;FWPV({8#lEv$&&GVakGus=@!3YVG`y^AO1m{2%Np;>HNA1e{=?ra1C}H zAwT0sbwG|!am;fl?*_t^^#yLDXZ*Nx)_FqueZi0c-G~omtpHW0Cu)mEJ`Z1X8brq$ z%vK##b~o*^b&Hz!hgrD=^6P8}aW40lhzMLB5T5*v`1QH?+L~-@CDi3+C@nRf2{7UE zyDIe{@LKw`Eu=Z%6<<_=#V|yxJIKiq_N?ZJ_v0$c)N4l07ZV_mIXG}glfBSPivOhw z-~+9GdckSpMBNR9eR`Y|9_)sXS+u_OiQ%!9rE(2AFjoxN8lk16Sb~^Sq6kRoEp3yD(mm`HsYIXcag_EAB8MHc}nahxVVUTts~U9P|f;7Ul$_` zStR4v&P4q_$KXOEni$lkxy8=9w8G&47VY0oDb^+jT+>ARe3NHUg~St`$RDxY)?;_F znqTujR&chZd2qHF7y8D$4&E3+e@J~!X3&BW4BF(Ebp#TEjrd+9SU!)j;qH+ZkL@AW z?J6Mj}v0_+D zH0qlbzCkHf|EZ`6c>5ig5NAFF%|La%M-}g(7&}Vx8K)qg30YD;H!S!??{;YivzrH0 z(M%2*b_S-)yh&Aiqai)GF^c!<1Xemj|13>dZ_M#)41SrP;OEMaRJ)bCeX*ZT7W`4Y zQ|8L@NHpD@Tf(5>1U(s5iW~Zdf7$@pAL`a3X@YUv1J>q-uJ_(Dy5nYTCUHC}1(dlI zt;5>DLcHh&jbysqt?G01MhXI3!8wgf){Hv}=0N|L$t8M#L7d6WscO8Om2|NBz2Ga^ zs86y%x$H18)~akOWD7@em7)ldlWgb?_sRN>-EcYQO_}aX@+b$dR{146>{kXWP4$nN{V0_+|3{Lt|8uX_fhKh~i{(x%cj*PU$i{PO(5$uA? zQzO>a6oPj-TUk&{zq?JD2MNb6Mf~V3g$ra+PB;ujLJ2JM(a7N*b`y{MX--!fAd}5C zF$D_b8S;+Np(!cW)(hnv5b@@|EMt*RLKF*wy>ykFhEhlPN~n_Bj>LT9B^_yj>z#fx z3JuE4H&?Cc!;G@}E*3k`HK#8ag`yE3Z1)5JUlSua%qkF zkTu|<9{w9OSi$qr)WD#7EzITnch=xnR63E*d~WGvi*Co9BBE?ETHud;!Z)7&wz+l6 zuKODYG1>I1U#a%&(GNJ`AqRfg=H!BtSl+_;CEeufF-#+*2EMMz-22@>18=8PH{PHd z);mN=aR0MPF>eutLiS#-AOX>#2%+pTGEOj!j4L(m0~&xR=0+g#HNpno6@veLhJp}e zyNVC$a>4;!9&iGvU_dj&xbKt@^t6r%f^)+}eV^suRTLP52+BVs0kOLwg6n`=NUv50E7My8XQUh?y%mW62OT1pMrKI3Q(r`7vU&@93=G~A?b(^pvC-8x=bSk zZ60BQR96WB1Z@9Df(M1IQh+YrU8sEjB=Tc2;(zBn-pete*icZE|M&Uc+oHg`|1o`g zH~m+k=D$o);{Rs)b<9Zo|9_Z6L6QHLNki(N>Dw^^i1LITprZeeqIaT#+)fw)PlllU zldphHC)t!0Gf(i9zgVm>`*TbmITF zH1FZ4{wrjRCx{t^26VK_2srZuWuY*EMAsMrJYFFCH35Ky7bq8<0K|ey2wHnrFMZyr z&^yEgX{{3i@&iE5>xKZ{Ads36G3a!i50D!C4?^~cLB<<|fc1!XN(HJRM)H^21sEs%vv+Mu0h*HkLHaEffMwc0n6)JhNXY#M5w@iO@dfXY z0c6dM2a4Hd1SA*#qYj@jK}uVgAZdaBj8t6uuhUNe>)ne9vfd#C6qLV9+@Q7{MnF#0 zJ7fd-ivG_~u3bVvOzpcw1u~ZSp8-kl(sunnX>L~*K-ByWDM2E8>;Si6kn^58AZQxI xVa^It*?521mj4+UJO?7%w*+`EfEcU=@KhDx-s^WzP+ae~{CgHDE&XryzW}Nww%-5% diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4ed3bdea4..5116c5b18 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip -distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c78733..a69d9cb6c 100755 --- a/gradlew +++ b/gradlew @@ -205,6 +205,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..53a6b238d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 7f21f6e80e7b452d0a37797538da14bd80d3fcc6 Mon Sep 17 00:00:00 2001 From: TacoTheDank Date: Tue, 9 Aug 2022 19:19:03 -0400 Subject: [PATCH 275/992] Update AGP and clojars Maven URL --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 322a47a6f..739f2e618 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:7.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -20,6 +20,6 @@ allprojects { google() mavenCentral() maven { url "https://jitpack.io" } - maven { url "https://clojars.org/repo" } + maven { url "https://repo.clojars.org" } } } From 2de33d8d075d17eb9885dd2c1302c80aa42a14c9 Mon Sep 17 00:00:00 2001 From: mhmdanas Date: Thu, 11 Aug 2022 19:24:14 +0300 Subject: [PATCH 276/992] Clarify that span shouldn't be in translated READMEs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3a34816d..52e6eef1a 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Also, since they are free and open source software, neither the app nor the Extr * Open in Kodi * Watch/Block age-restricted material - + ## Installation and updates From 737a331c85ba8387535c14e5882f46c336cf8061 Mon Sep 17 00:00:00 2001 From: mhmdanas Date: Thu, 11 Aug 2022 19:34:23 +0300 Subject: [PATCH 277/992] Remove extra whitespace from issue and PR templates --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +++--- .github/ISSUE_TEMPLATE/feature_request.yml | 3 +-- .github/ISSUE_TEMPLATE/question.yml | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3abb1fbb1..37ce61376 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -40,7 +40,7 @@ body: label: Steps to reproduce the bug description: | What did you do for the bug to show up? - + If you can't cause the bug to show up again reliably (and hence don't have a proper set of steps to give us), please still try to give as many details as possible on how you think you encountered the bug. placeholder: | 1. Go to '...' @@ -69,11 +69,11 @@ body: label: Screenshots/Screen recordings description: | A picture or video is worth a thousand words. - + If applicable, add screenshots or a screen recording to help explain your problem. GitHub supports uploading them directly in the text box. If your file is too big for Github to accept, try to compress it (ZIP-file) or feel free to paste a link to an image/video hoster here instead. - + :heavy_exclamation_mark: DON'T POST SCREENSHOTS OF THE ERROR PAGE. Instead, follow the instructions in the "Logs" section below. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9fc3c1632..7290d8c5b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -8,7 +8,6 @@ body: Thank you for helping to make NewPipe better by suggesting a feature. :hugs: Your ideas are highly welcome! The app is made for you, the users, after all. - - type: checkboxes id: checklist attributes: @@ -43,7 +42,7 @@ body: Describe any problem or limitation you come across while using the app which would be solved by this feature. validations: required: true - + - type: textarea id: additional-information attributes: diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 8cf22d8af..f78e2afc6 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -27,7 +27,7 @@ body: label: What is/are your question(s)? validations: required: true - + - type: textarea id: additional-information attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 10e40af2a..abc1665eb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -25,7 +25,7 @@ - -#### APK testing +#### APK testing The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR. From ec3efea05a37d813a036be2c6e2ddc01bd4eb487 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Fri, 12 Aug 2022 23:41:12 +0200 Subject: [PATCH 278/992] Update NewPipe Extractor to fix YouTube playback issues --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 496533c41..593835e06 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:5219a705bab539cf8c6624d0cec216e76e85f0b1' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:76aad92fa54524f20c3338ab568c9cd6b50c9d33' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" From cde32a8aedf80be4391d09d1a31a71fb358a3ca1 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Fri, 12 Aug 2022 23:49:07 +0200 Subject: [PATCH 279/992] Add changelog for v0.23.2 (988) --- fastlane/metadata/android/en-US/changelogs/988.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/988.txt diff --git a/fastlane/metadata/android/en-US/changelogs/988.txt b/fastlane/metadata/android/en-US/changelogs/988.txt new file mode 100644 index 000000000..5ef8fbbde --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Fix "Could not get any stream" error when trying to play any video +[YouTube] Fix "The Following content is not available on this app." message shown instead of the video requested \ No newline at end of file From fcaebc838ec59622396febc1e0a083f458ca6056 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Fri, 12 Aug 2022 23:50:41 +0200 Subject: [PATCH 280/992] Release v0.23.2 (988) --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 593835e06..14f63b7e0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { resValue "string", "app_name", "NewPipe" minSdk 19 targetSdk 29 - versionCode 987 - versionName "0.23.1" + versionCode 988 + versionName "0.23.2" multiDexEnabled true From 697b8411dfe8a8a0e62f7f0225e00944d5d01d14 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 12 Aug 2022 21:17:42 +0530 Subject: [PATCH 281/992] Use Okio's ByteString. --- .../giga/ui/adapter/MissionAdapter.java | 56 ++++++++----------- .../java/us/shandian/giga/util/Utility.java | 55 +++++------------- 2 files changed, 39 insertions(+), 72 deletions(-) diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java index 343b13ef8..808928370 100644 --- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java +++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java @@ -1,5 +1,25 @@ package us.shandian.giga.ui.adapter; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; +import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; +import static us.shandian.giga.get.DownloadMission.ERROR_CONNECT_HOST; +import static us.shandian.giga.get.DownloadMission.ERROR_FILE_CREATION; +import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_NO_CONTENT; +import static us.shandian.giga.get.DownloadMission.ERROR_INSUFFICIENT_STORAGE; +import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; +import static us.shandian.giga.get.DownloadMission.ERROR_PATH_CREATION; +import static us.shandian.giga.get.DownloadMission.ERROR_PERMISSION_DENIED; +import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING; +import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_HOLD; +import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_STOPPED; +import static us.shandian.giga.get.DownloadMission.ERROR_PROGRESS_LOST; +import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; +import static us.shandian.giga.get.DownloadMission.ERROR_SSL_EXCEPTION; +import static us.shandian.giga.get.DownloadMission.ERROR_TIMEOUT; +import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_EXCEPTION; +import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_HOST; + import android.annotation.SuppressLint; import android.app.NotificationManager; import android.content.Context; @@ -10,7 +30,6 @@ import android.os.Build; import android.os.Handler; import android.os.Message; import android.util.Log; -import android.util.SparseArray; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.Menu; @@ -38,10 +57,11 @@ import com.google.android.material.snackbar.Snackbar; import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.R; -import org.schabi.newpipe.error.ErrorUtil; -import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.error.ErrorInfo; +import org.schabi.newpipe.error.ErrorUtil; import org.schabi.newpipe.error.UserAction; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.streams.io.StoredFileHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.external_communication.ShareUtils; @@ -60,47 +80,19 @@ import us.shandian.giga.get.DownloadMission; import us.shandian.giga.get.FinishedMission; import us.shandian.giga.get.Mission; import us.shandian.giga.get.MissionRecoveryInfo; -import org.schabi.newpipe.streams.io.StoredFileHelper; import us.shandian.giga.service.DownloadManager; import us.shandian.giga.service.DownloadManagerService; import us.shandian.giga.ui.common.Deleter; import us.shandian.giga.ui.common.ProgressDrawable; import us.shandian.giga.util.Utility; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; -import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION; -import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; -import static us.shandian.giga.get.DownloadMission.ERROR_CONNECT_HOST; -import static us.shandian.giga.get.DownloadMission.ERROR_FILE_CREATION; -import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_NO_CONTENT; -import static us.shandian.giga.get.DownloadMission.ERROR_INSUFFICIENT_STORAGE; -import static us.shandian.giga.get.DownloadMission.ERROR_NOTHING; -import static us.shandian.giga.get.DownloadMission.ERROR_PATH_CREATION; -import static us.shandian.giga.get.DownloadMission.ERROR_PERMISSION_DENIED; -import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING; -import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_HOLD; -import static us.shandian.giga.get.DownloadMission.ERROR_POSTPROCESSING_STOPPED; -import static us.shandian.giga.get.DownloadMission.ERROR_PROGRESS_LOST; -import static us.shandian.giga.get.DownloadMission.ERROR_RESOURCE_GONE; -import static us.shandian.giga.get.DownloadMission.ERROR_SSL_EXCEPTION; -import static us.shandian.giga.get.DownloadMission.ERROR_TIMEOUT; -import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_EXCEPTION; -import static us.shandian.giga.get.DownloadMission.ERROR_UNKNOWN_HOST; - public class MissionAdapter extends Adapter implements Handler.Callback { - private static final SparseArray ALGORITHMS = new SparseArray<>(); private static final String TAG = "MissionAdapter"; private static final String UNDEFINED_PROGRESS = "--.-%"; private static final String DEFAULT_MIME_TYPE = "*/*"; private static final String UNDEFINED_ETA = "--:--"; - private static final int HASH_NOTIFICATION_ID = 123790; - static { - ALGORITHMS.put(R.id.md5, "MD5"); - ALGORITHMS.put(R.id.sha1, "SHA1"); - } - private final Context mContext; private final LayoutInflater mInflater; private final DownloadManager mDownloadManager; @@ -697,7 +689,7 @@ public class MissionAdapter extends Adapter implements Handler.Callb .build()); final StoredFileHelper storage = h.item.mission.storage; compositeDisposable.add( - Observable.fromCallable(() -> Utility.checksum(storage, ALGORITHMS.get(id))) + Observable.fromCallable(() -> Utility.checksum(storage, id)) .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java index 9e6787d5d..4cd424ab9 100644 --- a/app/src/main/java/us/shandian/giga/util/Utility.java +++ b/app/src/main/java/us/shandian/giga/util/Utility.java @@ -13,8 +13,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; +import com.google.android.exoplayer2.util.Util; + import org.schabi.newpipe.R; -import org.schabi.newpipe.streams.io.SharpStream; +import org.schabi.newpipe.streams.io.SharpInputStream; +import org.schabi.newpipe.streams.io.StoredFileHelper; import java.io.BufferedOutputStream; import java.io.File; @@ -25,11 +28,9 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.HttpURLConnection; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.Locale; -import org.schabi.newpipe.streams.io.StoredFileHelper; +import okio.ByteString; public class Utility { @@ -203,44 +204,18 @@ public class Utility { Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show(); } - public static String checksum(StoredFileHelper source, String algorithm) { - MessageDigest md; - - try { - md = MessageDigest.getInstance(algorithm); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); + public static String checksum(final StoredFileHelper source, final int algorithmId) + throws IOException { + ByteString byteString; + try (var inputStream = new SharpInputStream(source.getStream())) { + byteString = ByteString.of(Util.toByteArray(inputStream)); } - - SharpStream i; - - try { - i = source.getStream(); - } catch (Exception e) { - throw new RuntimeException(e); + if (algorithmId == R.id.md5) { + byteString = byteString.md5(); + } else if (algorithmId == R.id.sha1) { + byteString = byteString.sha1(); } - - byte[] buf = new byte[1024]; - int len; - - try { - while ((len = i.read(buf)) != -1) { - md.update(buf, 0, len); - } - } catch (IOException e) { - // nothing to do - } - - byte[] digest = md.digest(); - - // HEX - StringBuilder sb = new StringBuilder(); - for (byte b : digest) { - sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); - } - - return sb.toString(); - + return byteString.hex(); } @SuppressWarnings("ResultOfMethodCallIgnored") From 4f6b5b3b89879a53039a868c2007bcbdbb3972f8 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 15:27:29 +0530 Subject: [PATCH 282/992] Use ListAdapter in PeertubeInstanceListFragment. --- .../PeertubeInstanceListFragment.java | 204 ++++++++---------- 1 file changed, 93 insertions(+), 111 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java index 92b9a0362..1158b3d83 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/PeertubeInstanceListFragment.java @@ -12,28 +12,27 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ProgressBar; import android.widget.RadioButton; -import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.AppCompatImageView; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.grack.nanojson.JsonStringWriter; import com.grack.nanojson.JsonWriter; import org.schabi.newpipe.R; import org.schabi.newpipe.databinding.DialogEditTextBinding; +import org.schabi.newpipe.databinding.FragmentInstanceListBinding; +import org.schabi.newpipe.databinding.ItemInstanceBinding; import org.schabi.newpipe.extractor.services.peertube.PeertubeInstance; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.PeertubeHelper; @@ -41,7 +40,6 @@ import org.schabi.newpipe.util.ThemeHelper; import java.util.ArrayList; import java.util.Collections; -import java.util.List; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; @@ -50,12 +48,11 @@ import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; public class PeertubeInstanceListFragment extends Fragment { - private final List instanceList = new ArrayList<>(); private PeertubeInstance selectedInstance; private String savedInstanceListKey; private InstanceListAdapter instanceListAdapter; - private ProgressBar progressBar; + private FragmentInstanceListBinding binding; private SharedPreferences sharedPreferences; private CompositeDisposable disposables = new CompositeDisposable(); @@ -71,7 +68,6 @@ public class PeertubeInstanceListFragment extends Fragment { sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); savedInstanceListKey = getString(R.string.peertube_instance_list_key); selectedInstance = PeertubeHelper.getCurrentInstance(); - updateInstanceList(); setHasOptionsMenu(true); } @@ -79,7 +75,8 @@ public class PeertubeInstanceListFragment extends Fragment { @Override public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_instance_list, container, false); + binding = FragmentInstanceListBinding.inflate(inflater, container, false); + return binding.getRoot(); } @Override @@ -87,26 +84,17 @@ public class PeertubeInstanceListFragment extends Fragment { @Nullable final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); - initViews(rootView); - } - - private void initViews(@NonNull final View rootView) { - final TextView instanceHelpTV = rootView.findViewById(R.id.instanceHelpTV); - instanceHelpTV.setText(getString(R.string.peertube_instance_url_help, + binding.instanceHelpTV.setText(getString(R.string.peertube_instance_url_help, getString(R.string.peertube_instance_list_url))); - - initButton(rootView); - - final RecyclerView listInstances = rootView.findViewById(R.id.instances); - listInstances.setLayoutManager(new LinearLayoutManager(requireContext())); + binding.addInstanceButton.setOnClickListener(v -> showAddItemDialog(requireContext())); + binding.instances.setLayoutManager(new LinearLayoutManager(requireContext())); final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); - itemTouchHelper.attachToRecyclerView(listInstances); + itemTouchHelper.attachToRecyclerView(binding.instances); instanceListAdapter = new InstanceListAdapter(requireContext(), itemTouchHelper); - listInstances.setAdapter(instanceListAdapter); - - progressBar = rootView.findViewById(R.id.loading_progress_bar); + binding.instances.setAdapter(instanceListAdapter); + instanceListAdapter.submitList(PeertubeHelper.getInstanceList(requireContext())); } @Override @@ -131,6 +119,12 @@ public class PeertubeInstanceListFragment extends Fragment { disposables = null; } + @Override + public void onDestroyView() { + binding = null; + super.onDestroyView(); + } + /*////////////////////////////////////////////////////////////////////////// // Menu //////////////////////////////////////////////////////////////////////////*/ @@ -156,11 +150,6 @@ public class PeertubeInstanceListFragment extends Fragment { // Utils //////////////////////////////////////////////////////////////////////////*/ - private void updateInstanceList() { - instanceList.clear(); - instanceList.addAll(PeertubeHelper.getInstanceList(requireContext())); - } - private void selectInstance(final PeertubeInstance instance) { selectedInstance = PeertubeHelper.selectInstance(instance, requireContext()); sharedPreferences.edit().putBoolean(Constants.KEY_MAIN_PAGE_CHANGE, true).apply(); @@ -168,7 +157,7 @@ public class PeertubeInstanceListFragment extends Fragment { private void saveChanges() { final JsonStringWriter jsonWriter = JsonWriter.string().object().array("instances"); - for (final PeertubeInstance instance : instanceList) { + for (final PeertubeInstance instance : instanceListAdapter.getCurrentList()) { jsonWriter.object(); jsonWriter.value("name", instance.getName()); jsonWriter.value("url", instance.getUrl()); @@ -179,28 +168,21 @@ public class PeertubeInstanceListFragment extends Fragment { } private void restoreDefaults() { - new AlertDialog.Builder(requireContext()) + final Context context = requireContext(); + new AlertDialog.Builder(context) .setTitle(R.string.restore_defaults) .setMessage(R.string.restore_defaults_confirmation) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.ok, (dialog, which) -> { sharedPreferences.edit().remove(savedInstanceListKey).apply(); selectInstance(PeertubeInstance.DEFAULT_INSTANCE); - updateInstanceList(); - instanceListAdapter.notifyDataSetChanged(); + instanceListAdapter.submitList(PeertubeHelper.getInstanceList(context)); }) .show(); } - private void initButton(final View rootView) { - final FloatingActionButton fab = rootView.findViewById(R.id.addInstanceButton); - fab.setOnClickListener(v -> - showAddItemDialog(requireContext())); - } - private void showAddItemDialog(final Context c) { - final DialogEditTextBinding dialogBinding = - DialogEditTextBinding.inflate(getLayoutInflater()); + final var dialogBinding = DialogEditTextBinding.inflate(getLayoutInflater()); dialogBinding.dialogEditText.setInputType( InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); dialogBinding.dialogEditText.setHint(R.string.peertube_instance_add_help); @@ -222,17 +204,17 @@ public class PeertubeInstanceListFragment extends Fragment { if (cleanUrl == null) { return; } - progressBar.setVisibility(View.VISIBLE); + binding.loadingProgressBar.setVisibility(View.VISIBLE); final Disposable disposable = Single.fromCallable(() -> { final PeertubeInstance instance = new PeertubeInstance(cleanUrl); instance.fetchInstanceMetaData(); return instance; }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) .subscribe((instance) -> { - progressBar.setVisibility(View.GONE); + binding.loadingProgressBar.setVisibility(View.GONE); add(instance); }, e -> { - progressBar.setVisibility(View.GONE); + binding.loadingProgressBar.setVisibility(View.GONE); Toast.makeText(getActivity(), R.string.peertube_instance_add_fail, Toast.LENGTH_SHORT).show(); }); @@ -255,7 +237,7 @@ public class PeertubeInstanceListFragment extends Fragment { return null; } // only allow if not already exists - for (final PeertubeInstance instance : instanceList) { + for (final PeertubeInstance instance : instanceListAdapter.getCurrentList()) { if (instance.getUrl().equals(cleanUrl)) { Toast.makeText(getActivity(), R.string.peertube_instance_add_exists, Toast.LENGTH_SHORT).show(); @@ -266,8 +248,9 @@ public class PeertubeInstanceListFragment extends Fragment { } private void add(final PeertubeInstance instance) { - instanceList.add(instance); - instanceListAdapter.notifyDataSetChanged(); + final var list = new ArrayList<>(instanceListAdapter.getCurrentList()); + list.add(instance); + instanceListAdapter.submitList(list); } private ItemTouchHelper.SimpleCallback getItemTouchCallback() { @@ -281,8 +264,7 @@ public class PeertubeInstanceListFragment extends Fragment { final long msSinceStartScroll) { final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); - final int minimumAbsVelocity = Math.max(12, - Math.abs(standardSpeed)); + final int minimumAbsVelocity = Math.max(12, Math.abs(standardSpeed)); return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); } @@ -316,17 +298,19 @@ public class PeertubeInstanceListFragment extends Fragment { final int swipeDir) { final int position = viewHolder.getBindingAdapterPosition(); // do not allow swiping the selected instance - if (instanceList.get(position).getUrl().equals(selectedInstance.getUrl())) { + if (instanceListAdapter.getCurrentList().get(position).getUrl() + .equals(selectedInstance.getUrl())) { instanceListAdapter.notifyItemChanged(position); return; } - instanceList.remove(position); - instanceListAdapter.notifyItemRemoved(position); + final var list = new ArrayList<>(instanceListAdapter.getCurrentList()); + list.remove(position); - if (instanceList.isEmpty()) { - instanceList.add(selectedInstance); - instanceListAdapter.notifyItemInserted(0); + if (list.isEmpty()) { + list.add(selectedInstance); } + + instanceListAdapter.submitList(list); } }; } @@ -336,96 +320,94 @@ public class PeertubeInstanceListFragment extends Fragment { //////////////////////////////////////////////////////////////////////////*/ private class InstanceListAdapter - extends RecyclerView.Adapter { + extends ListAdapter { private final LayoutInflater inflater; private final ItemTouchHelper itemTouchHelper; private RadioButton lastChecked; InstanceListAdapter(final Context context, final ItemTouchHelper itemTouchHelper) { + super(new PeertubeInstanceCallback()); this.itemTouchHelper = itemTouchHelper; this.inflater = LayoutInflater.from(context); } public void swapItems(final int fromPosition, final int toPosition) { - Collections.swap(instanceList, fromPosition, toPosition); - notifyItemMoved(fromPosition, toPosition); + final var list = new ArrayList<>(getCurrentList()); + Collections.swap(list, fromPosition, toPosition); + submitList(list); } @NonNull @Override public InstanceListAdapter.TabViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - final View view = inflater.inflate(R.layout.item_instance, parent, false); - return new InstanceListAdapter.TabViewHolder(view); + return new InstanceListAdapter.TabViewHolder(ItemInstanceBinding.inflate(inflater, + parent, false)); } @Override public void onBindViewHolder(@NonNull final InstanceListAdapter.TabViewHolder holder, final int position) { - holder.bind(position, holder); - } - - @Override - public int getItemCount() { - return instanceList.size(); + holder.bind(position); } class TabViewHolder extends RecyclerView.ViewHolder { - private final AppCompatImageView instanceIconView; - private final TextView instanceNameView; - private final TextView instanceUrlView; - private final RadioButton instanceRB; - private final ImageView handle; + private final ItemInstanceBinding itemBinding; - TabViewHolder(final View itemView) { - super(itemView); - - instanceIconView = itemView.findViewById(R.id.instanceIcon); - instanceNameView = itemView.findViewById(R.id.instanceName); - instanceUrlView = itemView.findViewById(R.id.instanceUrl); - instanceRB = itemView.findViewById(R.id.selectInstanceRB); - handle = itemView.findViewById(R.id.handle); + TabViewHolder(final ItemInstanceBinding binding) { + super(binding.getRoot()); + this.itemBinding = binding; } @SuppressLint("ClickableViewAccessibility") - void bind(final int position, final TabViewHolder holder) { - handle.setOnTouchListener(getOnTouchListener(holder)); - - final PeertubeInstance instance = instanceList.get(position); - instanceNameView.setText(instance.getName()); - instanceUrlView.setText(instance.getUrl()); - instanceRB.setOnCheckedChangeListener(null); - if (selectedInstance.getUrl().equals(instance.getUrl())) { - if (lastChecked != null && lastChecked != instanceRB) { - lastChecked.setChecked(false); - } - instanceRB.setChecked(true); - lastChecked = instanceRB; - } - instanceRB.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) { - selectInstance(instance); - if (lastChecked != null && lastChecked != instanceRB) { - lastChecked.setChecked(false); - } - lastChecked = instanceRB; - } - }); - instanceIconView.setImageResource(R.drawable.ic_placeholder_peertube); - } - - @SuppressLint("ClickableViewAccessibility") - private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) { - return (view, motionEvent) -> { + void bind(final int position) { + itemBinding.handle.setOnTouchListener((view, motionEvent) -> { if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) { if (itemTouchHelper != null && getItemCount() > 1) { - itemTouchHelper.startDrag(item); + itemTouchHelper.startDrag(this); return true; } } return false; - }; + }); + + final PeertubeInstance instance = getItem(position); + itemBinding.instanceName.setText(instance.getName()); + itemBinding.instanceUrl.setText(instance.getUrl()); + itemBinding.selectInstanceRB.setOnCheckedChangeListener(null); + if (selectedInstance.getUrl().equals(instance.getUrl())) { + if (lastChecked != null && lastChecked != itemBinding.selectInstanceRB) { + lastChecked.setChecked(false); + } + itemBinding.selectInstanceRB.setChecked(true); + lastChecked = itemBinding.selectInstanceRB; + } + itemBinding.selectInstanceRB.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + selectInstance(instance); + if (lastChecked != null && lastChecked != itemBinding.selectInstanceRB) { + lastChecked.setChecked(false); + } + lastChecked = itemBinding.selectInstanceRB; + } + }); + itemBinding.instanceIcon.setImageResource(R.drawable.ic_placeholder_peertube); } } } + + private static class PeertubeInstanceCallback extends DiffUtil.ItemCallback { + @Override + public boolean areItemsTheSame(@NonNull final PeertubeInstance oldItem, + @NonNull final PeertubeInstance newItem) { + return oldItem.getUrl().equals(newItem.getUrl()); + } + + @Override + public boolean areContentsTheSame(@NonNull final PeertubeInstance oldItem, + @NonNull final PeertubeInstance newItem) { + return oldItem.getName().equals(newItem.getName()) + && oldItem.getUrl().equals(newItem.getUrl()); + } + } } From f9443f74217350c44ca3e4b696108199ff2cf3a1 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Fri, 29 Jul 2022 06:40:55 +0530 Subject: [PATCH 283/992] Refactor removeWatchedStreams() in LocalPlaylistFragment. --- .../local/playlist/LocalPlaylistFragment.java | 56 +++++++------------ 1 file changed, 21 insertions(+), 35 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 11d54f1ef..2938fe0b5 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 @@ -11,6 +11,7 @@ import android.os.Parcelable; import android.text.InputType; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -34,7 +35,6 @@ import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.history.model.StreamHistoryEntry; import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; import org.schabi.newpipe.database.stream.model.StreamEntity; -import org.schabi.newpipe.database.stream.model.StreamStateEntity; import org.schabi.newpipe.databinding.DialogEditTextBinding; import org.schabi.newpipe.databinding.LocalPlaylistHeaderBinding; import org.schabi.newpipe.databinding.PlaylistControlBinding; @@ -55,7 +55,6 @@ import org.schabi.newpipe.util.external_communication.ShareUtils; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -63,7 +62,6 @@ import java.util.stream.Collectors; import icepick.State; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; @@ -309,7 +307,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment> getPlaylistObserver() { - return new Subscriber>() { + return new Subscriber<>() { @Override public void onSubscribe(final Subscription s) { showLoading(); @@ -395,31 +393,19 @@ public class LocalPlaylistFragment extends BaseLocalListFragment playlist) -> { - // Playlist data - final Iterator playlistIter = playlist.iterator(); - - // History data - final HistoryRecordManager recordManager = - new HistoryRecordManager(getContext()); - final Iterator historyIter = recordManager - .getStreamHistorySortedById().blockingFirst().iterator(); - + final var recordManager = new HistoryRecordManager(getContext()); + final var historyIdsFlowable = recordManager.getStreamHistorySortedById() + // already sorted by ^ getStreamHistorySortedById(), binary search can be used + .map(historyList -> historyList.stream().map(StreamHistoryEntry::getStreamId) + .collect(Collectors.toList())); + final var streamsFlowable = playlistManager.getPlaylistStreams(playlistId) + .zipWith(historyIdsFlowable, (playlist, historyStreamIds) -> { // Remove Watched, Functionality data final List notWatchedItems = new ArrayList<>(); boolean thumbnailVideoRemoved = false; - // already sorted by ^ getStreamHistorySortedById(), binary search can be used - final ArrayList historyStreamIds = new ArrayList<>(); - while (historyIter.hasNext()) { - historyStreamIds.add(historyIter.next().getStreamId()); - } - if (removePartiallyWatched) { - while (playlistIter.hasNext()) { - final PlaylistStreamEntry playlistItem = playlistIter.next(); + for (final var playlistItem : playlist) { final int indexInHistory = Collections.binarySearch(historyStreamIds, playlistItem.getStreamId()); @@ -432,14 +418,15 @@ public class LocalPlaylistFragment extends BaseLocalListFragment streamStatesIter = recordManager - .loadLocalStreamStateBatch(playlist).blockingGet().iterator(); + final var streamStates = recordManager.loadLocalStreamStateBatch(playlist) + .blockingGet(); + + for (int i = 0; i < playlist.size(); i++) { + final var playlistItem = playlist.get(i); + final var streamStateEntity = streamStates.get(i); - while (playlistIter.hasNext()) { - final PlaylistStreamEntry playlistItem = playlistIter.next(); final int indexInHistory = Collections.binarySearch(historyStreamIds, playlistItem.getStreamId()); - final StreamStateEntity streamStateEntity = streamStatesIter.next(); final long duration = playlistItem.toStreamInfoItem().getDuration(); if (indexInHistory < 0 || (streamStateEntity != null @@ -453,19 +440,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment(notWatchedItems, thumbnailVideoRemoved); + }); + disposables.add(streamsFlowable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(flow -> { - final List notWatchedItems = - (List) flow.blockingFirst(); - final boolean thumbnailVideoRemoved = (Boolean) flow.blockingLast(); + final List notWatchedItems = flow.first; + final boolean thumbnailVideoRemoved = flow.second; itemListAdapter.clearStreamItemList(); itemListAdapter.addItems(notWatchedItems); saveChanges(); - if (thumbnailVideoRemoved) { updateThumbnailUrl(); } From 6399e3950711e63d7fe4d5b299a8091d4327f4f4 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 7 Aug 2022 13:40:10 +0530 Subject: [PATCH 284/992] Remove from playlist only upon selecting the option and not afterwards. --- .../local/playlist/LocalPlaylistFragment.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 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 2938fe0b5..9ace11a46 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 @@ -394,12 +394,14 @@ public class LocalPlaylistFragment extends BaseLocalListFragment historyList.stream().map(StreamHistoryEntry::getStreamId) .collect(Collectors.toList())); - final var streamsFlowable = playlistManager.getPlaylistStreams(playlistId) - .zipWith(historyIdsFlowable, (playlist, historyStreamIds) -> { + final var streamsMaybe = playlistManager.getPlaylistStreams(playlistId) + .firstElement() + .zipWith(historyIdsMaybe, (playlist, historyStreamIds) -> { // Remove Watched, Functionality data final List notWatchedItems = new ArrayList<>(); boolean thumbnailVideoRemoved = false; @@ -418,8 +420,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment(notWatchedItems, thumbnailVideoRemoved); }); - disposables.add(streamsFlowable.subscribeOn(Schedulers.io()) + + disposables.add(streamsMaybe.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(flow -> { final List notWatchedItems = flow.first; From 408a71cfdcdace8028070a2cd6f4476d38342366 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 3 Aug 2022 06:33:54 +0530 Subject: [PATCH 285/992] Calculate search score using streams. Co-authored-by: Stypox --- .../PreferenceFuzzySearchFunction.java | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java index ea45c68d2..68b0010c4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceFuzzySearchFunction.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.settings.preferencesearch; import android.text.TextUtils; +import android.util.Pair; import org.apache.commons.text.similarity.FuzzyScore; @@ -8,6 +9,7 @@ import java.util.Comparator; import java.util.Locale; import java.util.Map; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; public class PreferenceFuzzySearchFunction @@ -72,39 +74,22 @@ public class PreferenceFuzzySearchFunction ); private final PreferenceSearchItem item; - private final float score; + private final double score; - FuzzySearchSpecificDTO( - final PreferenceSearchItem item, - final String keyword) { + FuzzySearchSpecificDTO(final PreferenceSearchItem item, final String keyword) { this.item = item; - - float attributeScoreSum = 0; - int countOfAttributesWithScore = 0; - for (final Map.Entry, Float> we - : WEIGHT_MAP.entrySet()) { - final String valueToProcess = we.getKey().apply(item); - if (valueToProcess.isEmpty()) { - continue; - } - - attributeScoreSum += - FUZZY_SCORE.fuzzyScore(valueToProcess, keyword) * we.getValue(); - countOfAttributesWithScore++; - } - - if (countOfAttributesWithScore != 0) { - this.score = attributeScoreSum / countOfAttributesWithScore; - } else { - this.score = 0; - } + this.score = WEIGHT_MAP.entrySet().stream() + .map(entry -> new Pair<>(entry.getKey().apply(item), entry.getValue())) + .filter(pair -> !pair.first.isEmpty()) + .collect(Collectors.averagingDouble(pair -> + FUZZY_SCORE.fuzzyScore(pair.first, keyword) * pair.second)); } public PreferenceSearchItem getItem() { return item; } - public float getScore() { + public double getScore() { return score; } } From 67669c286b4bad85fef0854ceb3cb3a3f45e471d Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 31 Jul 2022 15:53:04 +0530 Subject: [PATCH 286/992] Use ListAdapter in SuggestionListAdapter. --- .../fragments/list/search/SearchFragment.java | 10 +-- .../list/search/SuggestionListAdapter.java | 88 +++++++------------ 2 files changed, 37 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 008163890..6827ddaaf 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -200,7 +200,7 @@ public class SearchFragment extends BaseListFragment()); + suggestionListAdapter.submitList(null); showKeyboardSearch(); }); @@ -945,7 +945,7 @@ public class SearchFragment extends BaseListFragment suggestionListAdapter.setItems(suggestions)); + suggestionListAdapter.submitList(suggestions); if (suggestionsPanelVisible && isErrorPanelVisible()) { hideLoading(); @@ -1066,14 +1066,14 @@ public class SearchFragment extends BaseListFragment { - private final ArrayList items = new ArrayList<>(); - private final Context context; + extends ListAdapter { private OnSuggestionItemSelected listener; - public SuggestionListAdapter(final Context context) { - this.context = context; - } - - public void setItems(final List items) { - this.items.clear(); - this.items.addAll(items); - notifyDataSetChanged(); + public SuggestionListAdapter() { + super(new SuggestionItemCallback()); } public void setListener(final OnSuggestionItemSelected listener) { @@ -39,45 +27,32 @@ public class SuggestionListAdapter @Override public SuggestionItemHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - return new SuggestionItemHolder(LayoutInflater.from(context) - .inflate(R.layout.item_search_suggestion, parent, false)); + return new SuggestionItemHolder(ItemSearchSuggestionBinding + .inflate(LayoutInflater.from(parent.getContext()), parent, false)); } @Override public void onBindViewHolder(final SuggestionItemHolder holder, final int position) { final SuggestionItem currentItem = getItem(position); holder.updateFrom(currentItem); - holder.queryView.setOnClickListener(v -> { + holder.itemBinding.suggestionSearch.setOnClickListener(v -> { if (listener != null) { listener.onSuggestionItemSelected(currentItem); } }); - holder.queryView.setOnLongClickListener(v -> { + holder.itemBinding.suggestionSearch.setOnLongClickListener(v -> { if (listener != null) { listener.onSuggestionItemLongClick(currentItem); } return true; }); - holder.insertView.setOnClickListener(v -> { + holder.itemBinding.suggestionInsert.setOnClickListener(v -> { if (listener != null) { listener.onSuggestionItemInserted(currentItem); } }); } - SuggestionItem getItem(final int position) { - return items.get(position); - } - - @Override - public int getItemCount() { - return items.size(); - } - - public boolean isEmpty() { - return getItemCount() == 0; - } - public interface OnSuggestionItemSelected { void onSuggestionItemSelected(SuggestionItem item); @@ -87,30 +62,31 @@ public class SuggestionListAdapter } public static final class SuggestionItemHolder extends RecyclerView.ViewHolder { - private final TextView itemSuggestionQuery; - private final ImageView suggestionIcon; - private final View queryView; - private final View insertView; + private final ItemSearchSuggestionBinding itemBinding; - // Cache some ids, as they can potentially be constantly updated/recycled - private final int historyResId; - private final int searchResId; - - private SuggestionItemHolder(final View rootView) { - super(rootView); - suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon); - itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query); - - queryView = rootView.findViewById(R.id.suggestion_search); - insertView = rootView.findViewById(R.id.suggestion_insert); - - historyResId = R.drawable.ic_history; - searchResId = R.drawable.ic_search; + private SuggestionItemHolder(final ItemSearchSuggestionBinding binding) { + super(binding.getRoot()); + this.itemBinding = binding; } private void updateFrom(final SuggestionItem item) { - suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId); - itemSuggestionQuery.setText(item.query); + itemBinding.itemSuggestionIcon.setImageResource(item.fromHistory ? R.drawable.ic_history + : R.drawable.ic_search); + itemBinding.itemSuggestionQuery.setText(item.query); + } + } + + private static class SuggestionItemCallback extends DiffUtil.ItemCallback { + @Override + public boolean areItemsTheSame(@NonNull final SuggestionItem oldItem, + @NonNull final SuggestionItem newItem) { + return oldItem.query.equals(newItem.query); + } + + @Override + public boolean areContentsTheSame(@NonNull final SuggestionItem oldItem, + @NonNull final SuggestionItem newItem) { + return oldItem.equals(newItem); } } } From 5e0788b99c7b0a43936f179cb94273d5046081c3 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 1 Aug 2022 06:11:35 +0530 Subject: [PATCH 287/992] Use ListAdapter in PreferenceSearchAdapter. --- .../PreferenceSearchAdapter.java | 61 +++++++++---------- .../PreferenceSearchFragment.java | 11 +--- .../preferencesearch/PreferenceSearcher.java | 3 +- 3 files changed, 34 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java index 02fbf9577..d6e2021a1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchAdapter.java @@ -1,54 +1,48 @@ package org.schabi.newpipe.settings.preferencesearch; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; import org.schabi.newpipe.databinding.SettingsPreferencesearchListItemResultBinding; -import java.util.ArrayList; -import java.util.List; import java.util.function.Consumer; class PreferenceSearchAdapter - extends RecyclerView.Adapter { - private List dataset = new ArrayList<>(); + extends ListAdapter { private Consumer onItemClickListener; + PreferenceSearchAdapter() { + super(new PreferenceCallback()); + } + @NonNull @Override - public PreferenceViewHolder onCreateViewHolder( - @NonNull final ViewGroup parent, - final int viewType - ) { - return new PreferenceViewHolder( - SettingsPreferencesearchListItemResultBinding.inflate( - LayoutInflater.from(parent.getContext()), - parent, - false)); + public PreferenceViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, + final int viewType) { + return new PreferenceViewHolder(SettingsPreferencesearchListItemResultBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); } @Override - public void onBindViewHolder( - @NonNull final PreferenceViewHolder holder, - final int position - ) { - final PreferenceSearchItem item = dataset.get(position); + public void onBindViewHolder(@NonNull final PreferenceViewHolder holder, final int position) { + final PreferenceSearchItem item = getItem(position); holder.binding.title.setText(item.getTitle()); - if (TextUtils.isEmpty(item.getSummary())) { + if (item.getSummary().isEmpty()) { holder.binding.summary.setVisibility(View.GONE); } else { holder.binding.summary.setVisibility(View.VISIBLE); holder.binding.summary.setText(item.getSummary()); } - if (TextUtils.isEmpty(item.getBreadcrumbs())) { + if (item.getBreadcrumbs().isEmpty()) { holder.binding.breadcrumbs.setVisibility(View.GONE); } else { holder.binding.breadcrumbs.setVisibility(View.VISIBLE); @@ -62,16 +56,6 @@ class PreferenceSearchAdapter }); } - void setContent(final List items) { - dataset = new ArrayList<>(items); - this.notifyDataSetChanged(); - } - - @Override - public int getItemCount() { - return dataset.size(); - } - void setOnItemClickListener(final Consumer onItemClickListener) { this.onItemClickListener = onItemClickListener; } @@ -84,4 +68,19 @@ class PreferenceSearchAdapter this.binding = binding; } } + + private static class PreferenceCallback extends DiffUtil.ItemCallback { + @Override + public boolean areItemsTheSame(@NonNull final PreferenceSearchItem oldItem, + @NonNull final PreferenceSearchItem newItem) { + return oldItem.getKey().equals(newItem.getKey()); + } + + @Override + public boolean areContentsTheSame(@NonNull final PreferenceSearchItem oldItem, + @NonNull final PreferenceSearchItem newItem) { + return oldItem.getAllRelevantSearchFields().equals(newItem + .getAllRelevantSearchFields()); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java index 308abbc4e..9d169d660 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java @@ -1,7 +1,6 @@ package org.schabi.newpipe.settings.preferencesearch; import android.os.Bundle; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -13,7 +12,6 @@ import androidx.recyclerview.widget.LinearLayoutManager; import org.schabi.newpipe.databinding.SettingsPreferencesearchFragmentBinding; -import java.util.ArrayList; import java.util.List; /** @@ -54,13 +52,8 @@ public class PreferenceSearchFragment extends Fragment { return; } - final List results = - !TextUtils.isEmpty(keyword) - ? searcher.searchFor(keyword) - : new ArrayList<>(); - - adapter.setContent(new ArrayList<>(results)); - + final List results = searcher.searchFor(keyword); + adapter.submitList(results); setEmptyViewShown(results.isEmpty()); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java index 176dc5d14..b3efc8dd1 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearcher.java @@ -3,6 +3,7 @@ package org.schabi.newpipe.settings.preferencesearch; import android.text.TextUtils; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -21,7 +22,7 @@ public class PreferenceSearcher { List searchFor(final String keyword) { if (TextUtils.isEmpty(keyword)) { - return new ArrayList<>(); + return Collections.emptyList(); } return configuration.getSearcher() From a50b9bd6ffde21b233a070b4887f1735572953e4 Mon Sep 17 00:00:00 2001 From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Date: Sun, 21 Aug 2022 17:39:57 +0000 Subject: [PATCH 288/992] Add FAQ entry to the template checklists (#8822) Co-authored-by: Mohammed Anas --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 2 ++ .github/ISSUE_TEMPLATE/question.yml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3abb1fbb1..d165daea3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -18,6 +18,8 @@ body: required: true - label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true + - label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my problem isn't listed." + required: true - label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise." required: true - label: "This issue contains only one bug." diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9fc3c1632..fbc61b579 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -16,6 +16,8 @@ body: options: - label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true + - label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my problem isn't listed." + required: true - label: "I'm aware that this is a request for NewPipe itself and that requests for adding a new service need to be made at [NewPipeExtractor](https://github.com/TeamNewPipe/NewPipeExtractor/issues)." required: true - label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise." diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index 8cf22d8af..458ed6f2e 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -16,6 +16,8 @@ body: options: - label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipe/issues) or [closed](https://github.com/TeamNewPipe/NewPipe/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true + - label: "I have read the [FAQ](https://newpipe.net/FAQ/) and my question isn't listed." + required: true - label: "I have taken the time to fill in all the required details. I understand that the question will be dismissed otherwise." required: true - label: "I have read and understood the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/dev/.github/CONTRIBUTING.md)." From db45042a566180fd9673e7a08f948fb4ebe16039 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 25 Aug 2022 10:14:46 +0200 Subject: [PATCH 289/992] Update NewPipeExtractor This removes the usage of the SourceVersion class, which was not available on Android and caused issues such as #8876 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 14f63b7e0..aff5cafa2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -190,7 +190,7 @@ dependencies { // name and the commit hash with the commit hash of the (pushed) commit you want to test // This works thanks to JitPack: https://jitpack.io/ implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751' - implementation 'com.github.TeamNewPipe:NewPipeExtractor:76aad92fa54524f20c3338ab568c9cd6b50c9d33' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:6a858368c86bc9a55abee586eb6c733e86c26b97' /** Checkstyle **/ checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" From 1a432f2ee39b1e5aee2700f0c1050067bebbf622 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 25 Aug 2022 10:15:30 +0200 Subject: [PATCH 290/992] Update jsoup to 1.15.3 This fixes a vulnerability issue related to Cross Site Scripting --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index aff5cafa2..610944236 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -231,7 +231,7 @@ dependencies { kapt "frankiesardo:icepick-processor:${icepickVersion}" // HTML parser - implementation "org.jsoup:jsoup:1.14.3" + implementation "org.jsoup:jsoup:1.15.3" // HTTP client //noinspection GradleDependency --> do not update okhttp to keep supporting Android 4.4 users From e1b8a3fbdfa754dc24b81d88932b3acaa4407a71 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 25 Aug 2022 10:16:56 +0200 Subject: [PATCH 291/992] Hotfix release v0.23.3 (989) --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 610944236..b297d5754 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { resValue "string", "app_name", "NewPipe" minSdk 19 targetSdk 29 - versionCode 988 - versionName "0.23.2" + versionCode 989 + versionName "0.23.3" multiDexEnabled true From 5c0ed22b093b0cc4735150ed213bc02fd77b93e6 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 25 Aug 2022 10:23:09 +0200 Subject: [PATCH 292/992] Add changelog for v0.23.3 (989) --- fastlane/metadata/android/en-US/changelogs/989.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/989.txt diff --git a/fastlane/metadata/android/en-US/changelogs/989.txt b/fastlane/metadata/android/en-US/changelogs/989.txt new file mode 100644 index 000000000..9ded366ea --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/989.txt @@ -0,0 +1,2 @@ +• [YouTube] Fix videos loading indefinitely +• Upgrade the jsoup library to 1.15.3, which includes a security fix From 4227866fcfc852b57aabfe03bf458e69e5a050ca Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 25 Aug 2022 10:44:16 +0200 Subject: [PATCH 293/992] Improve changelog for v0.23.3 (989) --- fastlane/metadata/android/en-US/changelogs/989.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fastlane/metadata/android/en-US/changelogs/989.txt b/fastlane/metadata/android/en-US/changelogs/989.txt index 9ded366ea..d1330ff8f 100644 --- a/fastlane/metadata/android/en-US/changelogs/989.txt +++ b/fastlane/metadata/android/en-US/changelogs/989.txt @@ -1,2 +1,3 @@ -• [YouTube] Fix videos loading indefinitely +• [YouTube] Fix infinite loading when trying to play any video +• [YouTube] Fix throttling on some videos • Upgrade the jsoup library to 1.15.3, which includes a security fix From 52dbfdee00d4ca4879f8915c5eea4a363aebdc35 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 15:06:19 +0200 Subject: [PATCH 294/992] Keep strong references to Picasso notification icon loading targets Before the Target would sometimes be garbage collected before being called with the loaded channel icon, since Picasso holds weak references to targets. This meant that sometimes a new streams notification would not be shown, because the lambda that should have shown it had already been garbage collected. --- .../feed/notifications/NotificationHelper.kt | 31 +++++++++++++++++-- .../schabi/newpipe/util/PicassoHelper.java | 28 +++-------------- 2 files changed, 32 insertions(+), 27 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 3a08b3e4a..351975486 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 @@ -4,6 +4,8 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.provider.Settings @@ -11,6 +13,8 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager +import com.squareup.picasso.Picasso +import com.squareup.picasso.Target import org.schabi.newpipe.R import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.local.feed.service.FeedUpdateInfo @@ -27,6 +31,8 @@ class NotificationHelper(val context: Context) { Context.NOTIFICATION_SERVICE ) as NotificationManager + private val iconLoadingTargets = ArrayList() + /** * Show a notification about new streams from a single channel. * Opening the notification will open the corresponding channel page. @@ -77,10 +83,29 @@ class NotificationHelper(val context: Context) { ) ) - PicassoHelper.loadNotificationIcon(data.avatarUrl) { bitmap -> - bitmap?.let { builder.setLargeIcon(it) } // set only if != null - manager.notify(data.pseudoId, builder.build()) + // a Target is like a listener for image loading events + val target = object : Target { + override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { + builder.setLargeIcon(bitmap) // set only if there is actually one + manager.notify(data.pseudoId, builder.build()) + iconLoadingTargets.remove(this) // allow it to be garbage-collected + } + + override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) { + manager.notify(data.pseudoId, builder.build()) + iconLoadingTargets.remove(this) // allow it to be garbage-collected + } + + override fun onPrepareLoad(placeHolderDrawable: Drawable) { + // Nothing to do + } } + + // add the target to the list to hold a strong reference and prevent it from being garbage + // collected, since Picasso only holds weak references to targets + iconLoadingTargets.add(target) + + PicassoHelper.loadNotificationIcon(data.avatarUrl).into(target) } companion object { diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 54140b0fb..fc7600d4b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -5,7 +5,6 @@ import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; import androidx.annotation.Nullable; @@ -14,7 +13,6 @@ import com.squareup.picasso.LruCache; import com.squareup.picasso.OkHttp3Downloader; import com.squareup.picasso.Picasso; import com.squareup.picasso.RequestCreator; -import com.squareup.picasso.Target; import com.squareup.picasso.Transformation; import org.schabi.newpipe.R; @@ -22,7 +20,6 @@ import org.schabi.newpipe.R; import java.io.File; import java.io.IOException; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import okhttp3.OkHttpClient; @@ -120,6 +117,10 @@ public final class PicassoHelper { return picassoInstance.load(url); } + public static RequestCreator loadNotificationIcon(final String url) { + return loadImageDefault(url, R.drawable.ic_newpipe_triangle_white); + } + public static RequestCreator loadScaledDownThumbnail(final Context context, final String url) { // scale down the notification thumbnail for performance @@ -170,27 +171,6 @@ public final class PicassoHelper { return picassoCache.get(imageUrl + "\n"); } - public static void loadNotificationIcon(final String url, - final Consumer bitmapConsumer) { - loadImageDefault(url, R.drawable.ic_newpipe_triangle_white) - .into(new Target() { - @Override - public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { - bitmapConsumer.accept(bitmap); - } - - @Override - public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - bitmapConsumer.accept(null); - } - - @Override - public void onPrepareLoad(final Drawable placeHolderDrawable) { - // Nothing to do - } - }); - } - private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { return loadImageDefault(url, placeholderResId, true); From c054ea07372ab33049047cbcfcf5ae5584c05dea Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 18:00:43 +0200 Subject: [PATCH 295/992] Create MediaSessionPlayerUi --- .../org/schabi/newpipe/player/Player.java | 43 +++-------- .../schabi/newpipe/player/PlayerService.java | 6 +- .../MediaSessionManager.java | 4 +- .../mediasession/MediaSessionPlayerUi.java | 74 +++++++++++++++++++ .../player/notification/NotificationUtil.java | 10 ++- .../schabi/newpipe/player/ui/PlayerUi.java | 3 +- .../newpipe/player/ui/PlayerUiList.java | 17 ++++- 7 files changed, 113 insertions(+), 44 deletions(-) rename app/src/main/java/org/schabi/newpipe/player/{helper => mediasession}/MediaSessionManager.java (97%) create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java 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 159d361c1..e1bc2f061 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -99,14 +99,13 @@ import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerServiceEventListener; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.LoadController; -import org.schabi.newpipe.player.helper.MediaSessionManager; import org.schabi.newpipe.player.helper.PlayerDataSource; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.mediaitem.MediaItemTag; +import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.player.notification.NotificationPlayerUi; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; -import org.schabi.newpipe.player.playback.PlayerMediaSession; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueItem; import org.schabi.newpipe.player.resolver.AudioPlaybackResolver; @@ -196,7 +195,6 @@ public final class Player implements PlaybackListener, Listener { private ExoPlayer simpleExoPlayer; private AudioReactor audioReactor; - private MediaSessionManager mediaSessionManager; @NonNull private final DefaultTrackSelector trackSelector; @NonNull private final LoadController loadController; @@ -225,7 +223,7 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ @SuppressWarnings("MemberName") // keep the unusual member name - private final PlayerUiList UIs = new PlayerUiList(); + private final PlayerUiList UIs; private BroadcastReceiver broadcastReceiver; private IntentFilter intentFilter; @@ -265,6 +263,15 @@ public final class Player implements PlaybackListener, Listener { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); + + // The UIs added here should always be present. They will be initialized when the player + // reaches the initialization step. Make sure the media session ui is before the + // 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 NotificationPlayerUi(this) + ); } private VideoPlaybackResolver.QualityResolver getQualityResolver() { @@ -431,11 +438,6 @@ public final class Player implements PlaybackListener, Listener { } private void initUIsForCurrentPlayerType() { - //noinspection SimplifyOptionalCallChains - if (!UIs.get(NotificationPlayerUi.class).isPresent()) { - UIs.addAndPrepare(new NotificationPlayerUi(this)); - } - if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN) || (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) { // correct UI already in place @@ -506,8 +508,6 @@ public final class Player implements PlaybackListener, Listener { simpleExoPlayer.setHandleAudioBecomingNoisy(true); audioReactor = new AudioReactor(context, simpleExoPlayer); - mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer, - new PlayerMediaSession(this)); registerBroadcastReceiver(); @@ -558,9 +558,6 @@ public final class Player implements PlaybackListener, Listener { if (playQueueManager != null) { playQueueManager.dispose(); } - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - } } public void destroy() { @@ -723,11 +720,6 @@ public final class Player implements PlaybackListener, Listener { Log.d(TAG, "ACTION_CONFIGURATION_CHANGED received"); } break; - case Intent.ACTION_HEADSET_PLUG: //FIXME - /*notificationManager.cancel(NOTIFICATION_ID); - mediaSessionManager.dispose(); - mediaSessionManager.enable(getBaseContext(), basePlayerImpl.simpleExoPlayer);*/ - break; } UIs.call(playerUi -> playerUi.onBroadcastReceived(intent)); @@ -1738,15 +1730,6 @@ public final class Player implements PlaybackListener, Listener { initThumbnail(info.getThumbnailUrl()); registerStreamViewed(); - final boolean showThumbnail = prefs.getBoolean( - context.getString(R.string.show_thumbnail_key), true); - mediaSessionManager.setMetadata( - getVideoTitle(), - getUploaderName(), - showThumbnail ? Optional.ofNullable(getThumbnail()) : Optional.empty(), - StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() - ); - notifyMetadataUpdateToListeners(); UIs.call(playerUi -> playerUi.onMetadataChanged(info)); } @@ -2194,10 +2177,6 @@ public final class Player implements PlaybackListener, Listener { return prefs; } - public MediaSessionManager getMediaSessionManager() { - return mediaSessionManager; - } - public PlayerType getPlayerType() { return playerType; 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 8d982617a..33b024e3d 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.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.util.ThemeHelper; @@ -73,9 +74,8 @@ public final class PlayerService extends Service { } player.handleIntent(intent); - if (player.getMediaSessionManager() != null) { - player.getMediaSessionManager().handleMediaButtonIntent(intent); - } + player.UIs().get(MediaSessionPlayerUi.class) + .ifPresent(ui -> ui.handleMediaButtonIntent(intent)); return START_NOT_STICKY; } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java similarity index 97% rename from app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java rename to app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index a8735dc08..61bc9e2e7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -1,4 +1,4 @@ -package org.schabi.newpipe.player.helper; +package org.schabi.newpipe.player.mediasession; import android.content.Context; import android.content.Intent; @@ -18,8 +18,6 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.player.mediasession.MediaSessionCallback; -import org.schabi.newpipe.player.mediasession.PlayQueueNavigator; import java.util.Optional; 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 new file mode 100644 index 000000000..d2de11ccf --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionPlayerUi.java @@ -0,0 +1,74 @@ +package org.schabi.newpipe.player.mediasession; + +import android.content.Intent; +import android.support.v4.media.session.MediaSessionCompat; + +import androidx.annotation.NonNull; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playback.PlayerMediaSession; +import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; + +import java.util.Optional; + +public class MediaSessionPlayerUi extends PlayerUi { + + private MediaSessionManager mediaSessionManager; + + public MediaSessionPlayerUi(@NonNull final Player player) { + super(player); + } + + @Override + public void initPlayer() { + super.initPlayer(); + if (mediaSessionManager != null) { + mediaSessionManager.dispose(); + } + mediaSessionManager = new MediaSessionManager(context, player.getExoPlayer(), + new PlayerMediaSession(player)); + } + + @Override + public void destroyPlayer() { + super.destroyPlayer(); + if (mediaSessionManager != null) { + mediaSessionManager.dispose(); + mediaSessionManager = null; + } + } + + @Override + public void onBroadcastReceived(final Intent intent) { + super.onBroadcastReceived(intent); + // TODO decide whether to handle ACTION_HEADSET_PLUG or not + } + + @Override + public void onMetadataChanged(@NonNull final StreamInfo info) { + super.onMetadataChanged(info); + + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + + mediaSessionManager.setMetadata( + player.getVideoTitle(), + player.getUploaderName(), + showThumbnail ? Optional.ofNullable(player.getThumbnail()) : Optional.empty(), + StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() + ); + } + + public void handleMediaButtonIntent(final Intent intent) { + if (mediaSessionManager != null) { + mediaSessionManager.handleMediaButtonIntent(intent); + } + } + + public Optional getSessionToken() { + return Optional.ofNullable(mediaSessionManager).map(MediaSessionManager::getSessionToken); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 1a91bc66d..29ec7a981 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -19,11 +19,13 @@ import androidx.core.content.ContextCompat; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi; import org.schabi.newpipe.util.NavigationHelper; import java.util.List; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static androidx.media.app.NotificationCompat.MediaStyle; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE; import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_CLOSE; @@ -101,9 +103,11 @@ public final class NotificationUtil { player.getContext(), player.getPrefs(), nonNothingSlotCount); final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); - builder.setStyle(new androidx.media.app.NotificationCompat.MediaStyle() - .setMediaSession(player.getMediaSessionManager().getSessionToken()) - .setShowActionsInCompactView(compactSlots)) + final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots); + player.UIs().get(MediaSessionPlayerUi.class).flatMap(MediaSessionPlayerUi::getSessionToken) + .ifPresent(mediaStyle::setMediaSession); + + builder.setStyle(mediaStyle) .setPriority(NotificationCompat.PRIORITY_HIGH) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setCategory(NotificationCompat.CATEGORY_TRANSPORT) diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java index 9ce04bfd5..57e2ec2a2 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUi.java @@ -29,7 +29,8 @@ public abstract class PlayerUi { @NonNull protected final Player player; /** - * @param player the player instance that will be usable throughout the lifetime of this UI + * @param player the player instance that will be usable throughout the lifetime of this UI; its + * context should already have been initialized */ protected PlayerUi(@NonNull final Player player) { this.context = player.getContext(); diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java index 05c0ed5b3..24fec3b8a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java +++ b/app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.java @@ -8,6 +8,19 @@ import java.util.function.Consumer; public final class PlayerUiList { final List playerUis = new ArrayList<>(); + /** + * Creates a {@link PlayerUiList} starting with the provided player uis. The provided player uis + * will not be prepared like those passed to {@link #addAndPrepare(PlayerUi)}, because when + * the {@link PlayerUiList} constructor is called, the player is still not running and it + * wouldn't make sense to initialize uis then. Instead the player will initialize them by doing + * proper calls to {@link #call(Consumer)}. + * + * @param initialPlayerUis the player uis this list should start with; the order will be kept + */ + public PlayerUiList(final PlayerUi... initialPlayerUis) { + playerUis.addAll(List.of(initialPlayerUis)); + } + /** * Adds the provided player ui to the list and calls on it the initialization functions that * apply based on the current player state. The preparation step needs to be done since when UIs @@ -67,11 +80,11 @@ public final class PlayerUiList { } /** - * Calls the provided consumer on all player UIs in the list. + * Calls the provided consumer on all player UIs in the list, in order of addition. * @param consumer the consumer to call with player UIs */ public void call(final Consumer consumer) { //noinspection SimplifyStreamApiCallChains - playerUis.stream().forEach(consumer); + playerUis.stream().forEachOrdered(consumer); } } From bc33322d4be00a0c34db81248e938074555874b7 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 22:40:21 +0200 Subject: [PATCH 296/992] Remove useless MediaSessionCallback The player is now passed directly, it made no sense to wrap around it in a callback that was not really a callback but rather, actually, a wrapper. --- .../mediasession/MediaSessionCallback.java | 21 ---- .../mediasession/MediaSessionManager.java | 16 +-- .../mediasession/MediaSessionPlayerUi.java | 4 +- .../mediasession/PlayQueueNavigator.java | 100 +++++++++++++----- .../player/playback/PlayerMediaSession.java | 99 ----------------- 5 files changed, 83 insertions(+), 157 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java delete mode 100644 app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java deleted file mode 100644 index c4b02d985..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.schabi.newpipe.player.mediasession; - -import android.support.v4.media.MediaDescriptionCompat; - -public interface MediaSessionCallback { - void playPrevious(); - - void playNext(); - - void playItemAtIndex(int index); - - int getCurrentPlayingIndex(); - - int getQueueSize(); - - MediaDescriptionCompat getQueueMetadata(int index); - - void play(); - - void pause(); -} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 61bc9e2e7..69f7d38fe 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -14,10 +14,11 @@ import androidx.annotation.Nullable; import androidx.media.session.MediaButtonReceiver; import com.google.android.exoplayer2.ForwardingPlayer; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.ui.VideoPlayerUi; import java.util.Optional; @@ -36,8 +37,7 @@ public class MediaSessionManager { private int lastAlbumArtHashCode; public MediaSessionManager(@NonNull final Context context, - @NonNull final Player player, - @NonNull final MediaSessionCallback callback) { + @NonNull final Player player) { mediaSession = new MediaSessionCompat(context, TAG); mediaSession.setActive(true); @@ -53,16 +53,18 @@ public class MediaSessionManager { .build()); sessionConnector = new MediaSessionConnector(mediaSession); - sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback)); - sessionConnector.setPlayer(new ForwardingPlayer(player) { + sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); + sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { @Override public void play() { - callback.play(); + player.play(); + // hide the player controls even if the play command came from the media session + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); } @Override public void pause() { - callback.pause(); + player.pause(); } }); } 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 d2de11ccf..a2eca575f 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 @@ -8,7 +8,6 @@ import androidx.annotation.NonNull; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.playback.PlayerMediaSession; import org.schabi.newpipe.player.ui.PlayerUi; import org.schabi.newpipe.util.StreamTypeUtil; @@ -28,8 +27,7 @@ public class MediaSessionPlayerUi extends PlayerUi { if (mediaSessionManager != null) { mediaSessionManager.dispose(); } - mediaSessionManager = new MediaSessionManager(context, player.getExoPlayer(), - new PlayerMediaSession(player)); + mediaSessionManager = new MediaSessionManager(context, player); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index 92cd425c5..7bd27bfdc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -1,106 +1,152 @@ package org.schabi.newpipe.player.mediasession; +import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; +import android.support.v4.media.MediaDescriptionCompat; +import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; + public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator { - public static final int DEFAULT_MAX_QUEUE_SIZE = 10; + private static final int MAX_QUEUE_SIZE = 10; private final MediaSessionCompat mediaSession; - private final MediaSessionCallback callback; - private final int maxQueueSize; + private final Player player; private long activeQueueItemId; public PlayQueueNavigator(@NonNull final MediaSessionCompat mediaSession, - @NonNull final MediaSessionCallback callback) { + @NonNull final Player player) { this.mediaSession = mediaSession; - this.callback = callback; - this.maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; + this.player = player; this.activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; } @Override - public long getSupportedQueueNavigatorActions(@Nullable final Player player) { + public long getSupportedQueueNavigatorActions( + @Nullable final com.google.android.exoplayer2.Player exoPlayer) { return ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS | ACTION_SKIP_TO_QUEUE_ITEM; } @Override - public void onTimelineChanged(@NonNull final Player player) { + public void onTimelineChanged(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { publishFloatingQueueWindow(); } @Override - public void onCurrentMediaItemIndexChanged(@NonNull final Player player) { + public void onCurrentMediaItemIndexChanged( + @NonNull final com.google.android.exoplayer2.Player exoPlayer) { if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID - || player.getCurrentTimeline().getWindowCount() > maxQueueSize) { + || exoPlayer.getCurrentTimeline().getWindowCount() > MAX_QUEUE_SIZE) { publishFloatingQueueWindow(); - } else if (!player.getCurrentTimeline().isEmpty()) { - activeQueueItemId = player.getCurrentMediaItemIndex(); + } else if (!exoPlayer.getCurrentTimeline().isEmpty()) { + activeQueueItemId = exoPlayer.getCurrentMediaItemIndex(); } } @Override - public long getActiveQueueItemId(@Nullable final Player player) { - return callback.getCurrentPlayingIndex(); + public long getActiveQueueItemId( + @Nullable final com.google.android.exoplayer2.Player exoPlayer) { + return Optional.ofNullable(player.getPlayQueue()).map(PlayQueue::getIndex).orElse(-1); } @Override - public void onSkipToPrevious(@NonNull final Player player) { - callback.playPrevious(); + public void onSkipToPrevious(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { + player.playPrevious(); } @Override - public void onSkipToQueueItem(@NonNull final Player player, final long id) { - callback.playItemAtIndex((int) id); + public void onSkipToQueueItem(@NonNull final com.google.android.exoplayer2.Player exoPlayer, + final long id) { + if (player.getPlayQueue() != null) { + player.selectQueueItem(player.getPlayQueue().getItem((int) id)); + } } @Override - public void onSkipToNext(@NonNull final Player player) { - callback.playNext(); + public void onSkipToNext(@NonNull final com.google.android.exoplayer2.Player exoPlayer) { + player.playNext(); } private void publishFloatingQueueWindow() { - if (callback.getQueueSize() == 0) { + final int windowCount = Optional.ofNullable(player.getPlayQueue()) + .map(PlayQueue::size) + .orElse(0); + if (windowCount == 0) { mediaSession.setQueue(Collections.emptyList()); activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; return; } // Yes this is almost a copypasta, got a problem with that? =\ - final int windowCount = callback.getQueueSize(); - final int currentWindowIndex = callback.getCurrentPlayingIndex(); - final int queueSize = Math.min(maxQueueSize, windowCount); + final int currentWindowIndex = player.getPlayQueue().getIndex(); + final int queueSize = Math.min(MAX_QUEUE_SIZE, windowCount); final int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0, windowCount - queueSize); final List queue = new ArrayList<>(); for (int i = startIndex; i < startIndex + queueSize; i++) { - queue.add(new MediaSessionCompat.QueueItem(callback.getQueueMetadata(i), i)); + queue.add(new MediaSessionCompat.QueueItem(getQueueMetadata(i), i)); } mediaSession.setQueue(queue); activeQueueItemId = currentWindowIndex; } + public MediaDescriptionCompat getQueueMetadata(final int index) { + if (player.getPlayQueue() == null) { + return null; + } + final PlayQueueItem item = player.getPlayQueue().getItem(index); + if (item == null) { + return null; + } + + final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() + .setMediaId(String.valueOf(index)) + .setTitle(item.getTitle()) + .setSubtitle(item.getUploader()); + + // set additional metadata for A2DP/AVRCP + final Bundle additionalMetadata = new Bundle(); + additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); + additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); + additionalMetadata + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); + additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); + additionalMetadata + .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); + descBuilder.setExtras(additionalMetadata); + + final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); + if (thumbnailUri != null) { + descBuilder.setIconUri(thumbnailUri); + } + + return descBuilder.build(); + } + @Override - public boolean onCommand(@NonNull final Player player, + public boolean onCommand(@NonNull final com.google.android.exoplayer2.Player exoPlayer, @NonNull final String command, @Nullable final Bundle extras, @Nullable final ResultReceiver cb) { diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java deleted file mode 100644 index 3c41acc75..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlayerMediaSession.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.schabi.newpipe.player.playback; - -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.media.MediaDescriptionCompat; -import android.support.v4.media.MediaMetadataCompat; - -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.mediasession.MediaSessionCallback; -import org.schabi.newpipe.player.playqueue.PlayQueueItem; -import org.schabi.newpipe.player.ui.VideoPlayerUi; - -public class PlayerMediaSession implements MediaSessionCallback { - private final Player player; - - public PlayerMediaSession(final Player player) { - this.player = player; - } - - @Override - public void playPrevious() { - player.playPrevious(); - } - - @Override - public void playNext() { - player.playNext(); - } - - @Override - public void playItemAtIndex(final int index) { - if (player.getPlayQueue() == null) { - return; - } - player.selectQueueItem(player.getPlayQueue().getItem(index)); - } - - @Override - public int getCurrentPlayingIndex() { - if (player.getPlayQueue() == null) { - return -1; - } - return player.getPlayQueue().getIndex(); - } - - @Override - public int getQueueSize() { - if (player.getPlayQueue() == null) { - return -1; - } - return player.getPlayQueue().size(); - } - - @Override - public MediaDescriptionCompat getQueueMetadata(final int index) { - if (player.getPlayQueue() == null) { - return null; - } - final PlayQueueItem item = player.getPlayQueue().getItem(index); - if (item == null) { - return null; - } - - final MediaDescriptionCompat.Builder descBuilder = new MediaDescriptionCompat.Builder() - .setMediaId(String.valueOf(index)) - .setTitle(item.getTitle()) - .setSubtitle(item.getUploader()); - - // set additional metadata for A2DP/AVRCP - final Bundle additionalMetadata = new Bundle(); - additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); - additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); - additionalMetadata - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); - additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); - additionalMetadata - .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); - descBuilder.setExtras(additionalMetadata); - - final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); - if (thumbnailUri != null) { - descBuilder.setIconUri(thumbnailUri); - } - - return descBuilder.build(); - } - - @Override - public void play() { - player.play(); - // hide the player controls even if the play command came from the media session - player.UIs().get(VideoPlayerUi.class).ifPresent(playerUi -> playerUi.hideControls(0, 0)); - } - - @Override - public void pause() { - player.pause(); - } -} From 3cc43e9fb9eb32063deebcfaf29531aa4588fc3d Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 22:44:59 +0200 Subject: [PATCH 297/992] Fix thumbnail sometimes not set to media session metadata The thumbnail was not being updated in the media session metadata after it was loaded, since there was no metadata update in that case, only a notification update. --- .../newpipe/player/mediasession/MediaSessionPlayerUi.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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 a2eca575f..e0343820e 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 @@ -1,9 +1,11 @@ package org.schabi.newpipe.player.mediasession; import android.content.Intent; +import android.graphics.Bitmap; import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; @@ -60,6 +62,12 @@ public class MediaSessionPlayerUi extends PlayerUi { ); } + @Override + public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { + super.onThumbnailLoaded(bitmap); + player.getCurrentStreamInfo().ifPresent(this::onMetadataChanged); + } + public void handleMediaButtonIntent(final Intent intent) { if (mediaSessionManager != null) { mediaSessionManager.handleMediaButtonIntent(intent); From f3a9b81b670c0e999a9d2fcc8b657d3e85e182f0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 23:03:42 +0200 Subject: [PATCH 298/992] Fix sometimes seeing outdated thumbnail in notification Before the thumbnail finishes loading for the new video the player is now playing, the old thumbnail was being used, leading to wrong thumbnails set in the media session and the notification. --- .../java/org/schabi/newpipe/player/Player.java | 14 +++++++++----- .../org/schabi/newpipe/util/PicassoHelper.java | 7 +++++++ 2 files changed, 16 insertions(+), 5 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 e1bc2f061..13dd1d938 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -748,11 +748,15 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail loading - private void initThumbnail(final String url) { + private void loadCurrentThumbnail(final String url) { if (DEBUG) { - Log.d(TAG, "Thumbnail - initThumbnail() called with url = [" + Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = [" + (url == null ? "null" : url) + "]"); } + + // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media + // session metadata while the new thumbnail is being loaded by Picasso. + currentThumbnail = null; if (isNullOrEmpty(url)) { return; } @@ -762,8 +766,8 @@ public final class Player implements PlaybackListener, Listener { @Override public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { if (DEBUG) { - Log.d(TAG, "Thumbnail - onLoadingComplete() called with: url = [" + url - + "], " + "loadedImage = [" + bitmap + " -> " + bitmap.getWidth() + "x" + Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: url = [" + url + + "], " + "bitmap = [" + bitmap + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" + from + "]"); } @@ -1727,7 +1731,7 @@ public final class Player implements PlaybackListener, Listener { maybeAutoQueueNextStream(info); - initThumbnail(info.getThumbnailUrl()); + loadCurrentThumbnail(info.getThumbnailUrl()); registerStreamViewed(); notifyMetadataUpdateToListeners(); diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index fc7600d4b..5739b930b 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -1,10 +1,12 @@ package org.schabi.newpipe.util; +import static org.schabi.newpipe.MainActivity.DEBUG; import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; +import android.util.Log; import androidx.annotation.Nullable; @@ -24,6 +26,7 @@ import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; public final class PicassoHelper { + private static final String TAG = PicassoHelper.class.getSimpleName(); public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; @@ -129,6 +132,10 @@ public final class PicassoHelper { .transform(new Transformation() { @Override public Bitmap transform(final Bitmap source) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - transform() called"); + } + final float notificationThumbnailWidth = Math.min( context.getResources() .getDimension(R.dimen.player_notification_thumbnail_width), From d73ca41cfe6dd3e39bf3f7d751d5870f307a336b Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 21 Jul 2022 23:44:17 +0200 Subject: [PATCH 299/992] Even when thumbnails should not be shown, set it to null in notification This makes sure the thumbnail is removed from the notification if the user disables thumbnails --- .../player/notification/NotificationUtil.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 29ec7a981..84e9cc3bc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -9,6 +9,7 @@ import android.os.Build; import android.util.Log; import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; @@ -137,12 +138,9 @@ public final class NotificationUtil { notificationBuilder.setContentTitle(player.getVideoTitle()); notificationBuilder.setContentText(player.getUploaderName()); notificationBuilder.setTicker(player.getVideoTitle()); + updateActions(notificationBuilder); - final boolean showThumbnail = player.getPrefs().getBoolean( - player.getContext().getString(R.string.show_thumbnail_key), true); - if (showThumbnail) { - setLargeIcon(notificationBuilder); - } + setLargeIcon(notificationBuilder); } @@ -344,17 +342,26 @@ public final class NotificationUtil { ///////////////////////////////////////////////////// private void setLargeIcon(final NotificationCompat.Builder builder) { + final boolean showThumbnail = player.getPrefs().getBoolean( + player.getContext().getString(R.string.show_thumbnail_key), true); + final Bitmap thumbnail = player.getThumbnail(); + if (thumbnail == null || !showThumbnail) { + // since the builder is reused, make sure the thumbnail is unset if there is not one + builder.setLargeIcon(null); + return; + } + final boolean scaleImageToSquareAspectRatio = player.getPrefs().getBoolean( player.getContext().getString(R.string.scale_to_square_image_in_notifications_key), false); if (scaleImageToSquareAspectRatio) { - builder.setLargeIcon(getBitmapWithSquareAspectRatio(player.getThumbnail())); + builder.setLargeIcon(getBitmapWithSquareAspectRatio(thumbnail)); } else { - builder.setLargeIcon(player.getThumbnail()); + builder.setLargeIcon(thumbnail); } } - private Bitmap getBitmapWithSquareAspectRatio(final Bitmap bitmap) { + private Bitmap getBitmapWithSquareAspectRatio(@NonNull final Bitmap bitmap) { // Find the smaller dimension and then take a center portion of the image that // has that size. final int w = bitmap.getWidth(); From 8bff445ec3d314a35d3243d4fff2711184265533 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 10:17:22 +0200 Subject: [PATCH 300/992] Remove useless checks before updating metadata A while ago NewPipe called the metadata update function very often, so checks were needed to ensure not wasting time updating metadata if it were already up to date. Now, instead, the metadata update function is called exactly when needed, i.e. when metadata changes, so such checks are not needed anymore (and were probably also a little resource-heavy). --- .../org/schabi/newpipe/player/Player.java | 5 - .../mediasession/MediaSessionManager.java | 123 ++---------------- .../mediasession/MediaSessionPlayerUi.java | 2 +- 3 files changed, 11 insertions(+), 119 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 13dd1d938..22d46bcbe 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -54,7 +54,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.util.Log; @@ -1773,10 +1772,6 @@ public final class Player implements PlaybackListener, Listener { @Nullable public Bitmap getThumbnail() { - if (currentThumbnail == null) { - currentThumbnail = BitmapFactory.decodeResource( - context.getResources(), R.drawable.placeholder_thumbnail_video); - } return currentThumbnail; } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 69f7d38fe..98b6d1b32 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -20,8 +20,6 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.VideoPlayerUi; -import java.util.Optional; - public class MediaSessionManager { private static final String TAG = MediaSessionManager.class.getSimpleName(); public static final boolean DEBUG = MainActivity.DEBUG; @@ -31,11 +29,6 @@ public class MediaSessionManager { @NonNull private final MediaSessionConnector sessionConnector; - private int lastTitleHashCode; - private int lastArtistHashCode; - private long lastDuration; - private int lastAlbumArtHashCode; - public MediaSessionManager(@NonNull final Context context, @NonNull final Player player) { mediaSession = new MediaSessionCompat(context, TAG); @@ -84,134 +77,38 @@ public class MediaSessionManager { * * @param title {@link MediaMetadataCompat#METADATA_KEY_TITLE} * @param artist {@link MediaMetadataCompat#METADATA_KEY_ARTIST} - * @param optAlbumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART} + * @param albumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART}, if not null * @param duration {@link MediaMetadataCompat#METADATA_KEY_DURATION} * - should be a negative value for unknown durations, e.g. for livestreams */ public void setMetadata(@NonNull final String title, @NonNull final String artist, - @NonNull final Optional optAlbumArt, - final long duration - ) { + @Nullable final Bitmap albumArt, + final long duration) { if (DEBUG) { - Log.d(TAG, "setMetadata called:" - + " t: " + title - + " a: " + artist - + " thumb: " + ( - optAlbumArt.isPresent() - ? optAlbumArt.get().hashCode() - : "") - + " d: " + duration); + Log.d(TAG, "setMetadata called with: title = [" + title + "], artist = [" + artist + + "], albumArt = [" + (albumArt == null ? "null" : albumArt.hashCode()) + + "], duration = [" + duration + "]"); } if (!mediaSession.isActive()) { if (DEBUG) { - Log.d(TAG, "setMetadata: mediaSession not active - exiting"); + Log.d(TAG, "setMetadata: media session not active, exiting"); } return; } - if (!checkIfMetadataShouldBeSet(title, artist, optAlbumArt, duration)) { - if (DEBUG) { - Log.d(TAG, "setMetadata: No update required - exiting"); - } - return; - } - - if (DEBUG) { - Log.d(TAG, "setMetadata: N_Metadata update:" - + " t: " + title - + " a: " + artist - + " thumb: " + ( - optAlbumArt.isPresent() - ? optAlbumArt.get().hashCode() - : "") - + " d: " + duration); - } - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - if (optAlbumArt.isPresent()) { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, optAlbumArt.get()); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, optAlbumArt.get()); + if (albumArt != null) { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArt); } mediaSession.setMetadata(builder.build()); - - lastTitleHashCode = title.hashCode(); - lastArtistHashCode = artist.hashCode(); - lastDuration = duration; - optAlbumArt.ifPresent(bitmap -> lastAlbumArtHashCode = bitmap.hashCode()); - } - - private boolean checkIfMetadataShouldBeSet( - @NonNull final String title, - @NonNull final String artist, - @NonNull final Optional optAlbumArt, - final long duration - ) { - // Check if the values have changed since the last time - if (title.hashCode() != lastTitleHashCode - || artist.hashCode() != lastArtistHashCode - || duration != lastDuration - || (optAlbumArt.isPresent() && optAlbumArt.get().hashCode() != lastAlbumArtHashCode) - ) { - if (DEBUG) { - Log.d(TAG, - "checkIfMetadataShouldBeSet: true - reason: changed values since last"); - } - return true; - } - - // Check if the currently set metadata is valid - if (getMetadataTitle() == null - || getMetadataArtist() == null - // Note that the duration can be <= 0 for live streams - ) { - if (DEBUG) { - if (getMetadataTitle() == null) { - Log.d(TAG, - "N_getMetadataTitle: title == null"); - } else if (getMetadataArtist() == null) { - Log.d(TAG, - "N_getMetadataArtist: artist == null"); - } - } - return true; - } - - // If we got an album art check if the current set AlbumArt is null - if (optAlbumArt.isPresent() && getMetadataAlbumArt() == null) { - if (DEBUG) { - Log.d(TAG, "N_getMetadataAlbumArt: thumb == null"); - } - return true; - } - - // Default - no update required - return false; - } - - - @Nullable - private Bitmap getMetadataAlbumArt() { - return mediaSession.getController().getMetadata() - .getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART); - } - - @Nullable - private String getMetadataTitle() { - return mediaSession.getController().getMetadata() - .getString(MediaMetadataCompat.METADATA_KEY_TITLE); - } - - @Nullable - private String getMetadataArtist() { - return mediaSession.getController().getMetadata() - .getString(MediaMetadataCompat.METADATA_KEY_ARTIST); } /** 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 e0343820e..2140be26d 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 @@ -57,7 +57,7 @@ public class MediaSessionPlayerUi extends PlayerUi { mediaSessionManager.setMetadata( player.getVideoTitle(), player.getUploaderName(), - showThumbnail ? Optional.ofNullable(player.getThumbnail()) : Optional.empty(), + showThumbnail ? player.getThumbnail() : null, StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() ); } From f80d1dc48dcc0539ac7faa20b085b0adc2c5fe64 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 11:31:49 +0200 Subject: [PATCH 301/992] Let exoplayer decide when to update metadata Though still make sure metadata is updated after the thumbnail is loaded. This fixes the wrong seekbar properties (duration and current position) being shown in the notification sometimes. --- .../mediasession/MediaSessionManager.java | 87 ++++++++----------- .../mediasession/MediaSessionPlayerUi.java | 22 +---- 2 files changed, 40 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java index 98b6d1b32..c6766fbcb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java @@ -2,10 +2,8 @@ package org.schabi.newpipe.player.mediasession; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; -import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; import android.view.KeyEvent; @@ -17,8 +15,12 @@ import com.google.android.exoplayer2.ForwardingPlayer; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import org.schabi.newpipe.MainActivity; +import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.VideoPlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; + +import java.util.Optional; public class MediaSessionManager { private static final String TAG = MediaSessionManager.class.getSimpleName(); @@ -34,17 +36,6 @@ public class MediaSessionManager { mediaSession = new MediaSessionCompat(context, TAG); mediaSession.setActive(true); - mediaSession.setPlaybackState(new PlaybackStateCompat.Builder() - .setState(PlaybackStateCompat.STATE_NONE, -1, 1) - .setActions(PlaybackStateCompat.ACTION_SEEK_TO - | PlaybackStateCompat.ACTION_PLAY - | PlaybackStateCompat.ACTION_PAUSE // was play and pause now play/pause - | PlaybackStateCompat.ACTION_SKIP_TO_NEXT - | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS - | PlaybackStateCompat.ACTION_SET_REPEAT_MODE - | PlaybackStateCompat.ACTION_STOP) - .build()); - sessionConnector = new MediaSessionConnector(mediaSession); sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { @@ -60,6 +51,37 @@ public class MediaSessionManager { player.pause(); } }); + + sessionConnector.setMetadataDeduplicationEnabled(true); + sessionConnector.setMediaMetadataProvider(exoPlayer -> { + if (DEBUG) { + Log.d(TAG, "MediaMetadataProvider#getMetadata called"); + } + + // set title and artist + final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); + + // set duration (-1 for livestreams, since they don't have a duration) + final long duration = player.getCurrentStreamInfo() + .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) + .map(info -> info.getDuration() * 1000L) + .orElse(-1L); + builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); + + // set album art, unless the user asked not to, or there is no thumbnail available + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + Optional.ofNullable(player.getThumbnail()) + .filter(bitmap -> showThumbnail) + .ifPresent(bitmap -> { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); + }); + + return builder.build(); + }); } @Nullable @@ -72,43 +94,8 @@ public class MediaSessionManager { return mediaSession.getSessionToken(); } - /** - * sets the Metadata - if required. - * - * @param title {@link MediaMetadataCompat#METADATA_KEY_TITLE} - * @param artist {@link MediaMetadataCompat#METADATA_KEY_ARTIST} - * @param albumArt {@link MediaMetadataCompat#METADATA_KEY_ALBUM_ART}, if not null - * @param duration {@link MediaMetadataCompat#METADATA_KEY_DURATION} - * - should be a negative value for unknown durations, e.g. for livestreams - */ - public void setMetadata(@NonNull final String title, - @NonNull final String artist, - @Nullable final Bitmap albumArt, - final long duration) { - if (DEBUG) { - Log.d(TAG, "setMetadata called with: title = [" + title + "], artist = [" + artist - + "], albumArt = [" + (albumArt == null ? "null" : albumArt.hashCode()) - + "], duration = [" + duration + "]"); - } - - if (!mediaSession.isActive()) { - if (DEBUG) { - Log.d(TAG, "setMetadata: media session not active, exiting"); - } - return; - } - - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - - if (albumArt != null) { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, albumArt); - } - - mediaSession.setMetadata(builder.build()); + void triggerMetadataUpdate() { + sessionConnector.invalidateMediaSessionMetadata(); } /** 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 2140be26d..c78a3a6b1 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 @@ -7,11 +7,8 @@ import android.support.v4.media.session.MediaSessionCompat; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.PlayerUi; -import org.schabi.newpipe.util.StreamTypeUtil; import java.util.Optional; @@ -47,25 +44,12 @@ public class MediaSessionPlayerUi extends PlayerUi { // TODO decide whether to handle ACTION_HEADSET_PLUG or not } - @Override - public void onMetadataChanged(@NonNull final StreamInfo info) { - super.onMetadataChanged(info); - - final boolean showThumbnail = player.getPrefs().getBoolean( - context.getString(R.string.show_thumbnail_key), true); - - mediaSessionManager.setMetadata( - player.getVideoTitle(), - player.getUploaderName(), - showThumbnail ? player.getThumbnail() : null, - StreamTypeUtil.isLiveStream(info.getStreamType()) ? -1 : info.getDuration() - ); - } - @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - player.getCurrentStreamInfo().ifPresent(this::onMetadataChanged); + if (mediaSessionManager != null) { + mediaSessionManager.triggerMetadataUpdate(); + } } public void handleMediaButtonIntent(final Intent intent) { From 11bd2369e5e8c0e5352fe76dada02089664f36c0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 12:00:32 +0200 Subject: [PATCH 302/992] Merge MediaSessionManager into MediaSessionPlayerUi --- .../mediasession/MediaSessionManager.java | 110 ------------------ .../mediasession/MediaSessionPlayerUi.java | 104 ++++++++++++++--- 2 files changed, 90 insertions(+), 124 deletions(-) delete mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java deleted file mode 100644 index c6766fbcb..000000000 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionManager.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.schabi.newpipe.player.mediasession; - -import android.content.Context; -import android.content.Intent; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.util.Log; -import android.view.KeyEvent; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.media.session.MediaButtonReceiver; - -import com.google.android.exoplayer2.ForwardingPlayer; -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; - -import org.schabi.newpipe.MainActivity; -import org.schabi.newpipe.R; -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.ui.VideoPlayerUi; -import org.schabi.newpipe.util.StreamTypeUtil; - -import java.util.Optional; - -public class MediaSessionManager { - private static final String TAG = MediaSessionManager.class.getSimpleName(); - public static final boolean DEBUG = MainActivity.DEBUG; - - @NonNull - private final MediaSessionCompat mediaSession; - @NonNull - private final MediaSessionConnector sessionConnector; - - public MediaSessionManager(@NonNull final Context context, - @NonNull final Player player) { - mediaSession = new MediaSessionCompat(context, TAG); - mediaSession.setActive(true); - - sessionConnector = new MediaSessionConnector(mediaSession); - sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player)); - sessionConnector.setPlayer(new ForwardingPlayer(player.getExoPlayer()) { - @Override - public void play() { - player.play(); - // hide the player controls even if the play command came from the media session - player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); - } - - @Override - public void pause() { - player.pause(); - } - }); - - sessionConnector.setMetadataDeduplicationEnabled(true); - sessionConnector.setMediaMetadataProvider(exoPlayer -> { - if (DEBUG) { - Log.d(TAG, "MediaMetadataProvider#getMetadata called"); - } - - // set title and artist - final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); - - // set duration (-1 for livestreams, since they don't have a duration) - final long duration = player.getCurrentStreamInfo() - .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) - .map(info -> info.getDuration() * 1000L) - .orElse(-1L); - builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); - - // set album art, unless the user asked not to, or there is no thumbnail available - final boolean showThumbnail = player.getPrefs().getBoolean( - context.getString(R.string.show_thumbnail_key), true); - Optional.ofNullable(player.getThumbnail()) - .filter(bitmap -> showThumbnail) - .ifPresent(bitmap -> { - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); - }); - - return builder.build(); - }); - } - - @Nullable - @SuppressWarnings("UnusedReturnValue") - public KeyEvent handleMediaButtonIntent(final Intent intent) { - return MediaButtonReceiver.handleIntent(mediaSession, intent); - } - - public MediaSessionCompat.Token getSessionToken() { - return mediaSession.getSessionToken(); - } - - void triggerMetadataUpdate() { - sessionConnector.invalidateMediaSessionMetadata(); - } - - /** - * Should be called on player destruction to prevent leakage. - */ - public void dispose() { - sessionConnector.setPlayer(null); - sessionConnector.setQueueNavigator(null); - mediaSession.setActive(false); - mediaSession.release(); - } -} 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 c78a3a6b1..92a137900 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 @@ -1,20 +1,33 @@ package org.schabi.newpipe.player.mediasession; +import static org.schabi.newpipe.MainActivity.DEBUG; + import android.content.Intent; import android.graphics.Bitmap; +import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.media.session.MediaButtonReceiver; +import com.google.android.exoplayer2.ForwardingPlayer; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; + +import org.schabi.newpipe.R; import org.schabi.newpipe.player.Player; import org.schabi.newpipe.player.ui.PlayerUi; +import org.schabi.newpipe.player.ui.VideoPlayerUi; +import org.schabi.newpipe.util.StreamTypeUtil; import java.util.Optional; public class MediaSessionPlayerUi extends PlayerUi { + private static final String TAG = "MediaSessUi"; - private MediaSessionManager mediaSessionManager; + private MediaSessionCompat mediaSession; + private MediaSessionConnector sessionConnector; public MediaSessionPlayerUi(@NonNull final Player player) { super(player); @@ -23,18 +36,31 @@ public class MediaSessionPlayerUi extends PlayerUi { @Override public void initPlayer() { super.initPlayer(); - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - } - mediaSessionManager = new MediaSessionManager(context, player); + 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()); + + sessionConnector.setMetadataDeduplicationEnabled(true); + sessionConnector.setMediaMetadataProvider(exoPlayer -> buildMediaMetadata()); } @Override public void destroyPlayer() { super.destroyPlayer(); - if (mediaSessionManager != null) { - mediaSessionManager.dispose(); - mediaSessionManager = null; + if (sessionConnector != null) { + sessionConnector.setPlayer(null); + sessionConnector.setQueueNavigator(null); + sessionConnector = null; + } + if (mediaSession != null) { + mediaSession.setActive(false); + mediaSession.release(); + mediaSession = null; } } @@ -47,18 +73,68 @@ public class MediaSessionPlayerUi extends PlayerUi { @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); - if (mediaSessionManager != null) { - mediaSessionManager.triggerMetadataUpdate(); + if (sessionConnector != null) { + // the thumbnail is now loaded: invalidate the metadata to trigger a metadata update + sessionConnector.invalidateMediaSessionMetadata(); } } + public void handleMediaButtonIntent(final Intent intent) { - if (mediaSessionManager != null) { - mediaSessionManager.handleMediaButtonIntent(intent); - } + MediaButtonReceiver.handleIntent(mediaSession, intent); } public Optional getSessionToken() { - return Optional.ofNullable(mediaSessionManager).map(MediaSessionManager::getSessionToken); + return Optional.ofNullable(mediaSession).map(MediaSessionCompat::getSessionToken); + } + + + private ForwardingPlayer getForwardingPlayer() { + // ForwardingPlayer means that all media session actions called on this player are + // forwarded directly to the connected exoplayer, except for the overridden methods. So + // override play and pause since our player adds more functionality to them over exoplayer. + return new ForwardingPlayer(player.getExoPlayer()) { + @Override + public void play() { + player.play(); + // hide the player controls even if the play command came from the media session + player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0)); + } + + @Override + public void pause() { + player.pause(); + } + }; + } + + private MediaMetadataCompat buildMediaMetadata() { + if (DEBUG) { + Log.d(TAG, "buildMediaMetadata called"); + } + + // set title and artist + final MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, player.getVideoTitle()) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, player.getUploaderName()); + + // set duration (-1 for livestreams or if unknown, see the METADATA_KEY_DURATION docs) + final long duration = player.getCurrentStreamInfo() + .filter(info -> !StreamTypeUtil.isLiveStream(info.getStreamType())) + .map(info -> info.getDuration() * 1000L) + .orElse(-1L); + builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration); + + // set album art, unless the user asked not to, or there is no thumbnail available + final boolean showThumbnail = player.getPrefs().getBoolean( + context.getString(R.string.show_thumbnail_key), true); + Optional.ofNullable(player.getThumbnail()) + .filter(bitmap -> showThumbnail) + .ifPresent(bitmap -> { + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap); + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap); + }); + + return builder.build(); } } From 510efaae976c1fe15a85c3f81c4a49573760097f Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 22 Jul 2022 14:39:25 +0200 Subject: [PATCH 303/992] Keep strong reference to Picasso thumbnail loading target Before the Target would sometimes be garbage collected before being called with the loaded thumbnail, since Picasso holds weak references to targets --- .../org/schabi/newpipe/player/Player.java | 79 ++++++++++++------- .../schabi/newpipe/util/PicassoHelper.java | 2 - 2 files changed, 51 insertions(+), 30 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 22d46bcbe..45b9b0fde 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -174,6 +174,7 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ public static final int RENDERER_UNAVAILABLE = -1; + private static final String PICASSO_PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; /*////////////////////////////////////////////////////////////////////////// // Playback @@ -232,6 +233,11 @@ public final class Player implements PlaybackListener, Listener { @NonNull private final SerialDisposable progressUpdateDisposable = new SerialDisposable(); @NonNull private final CompositeDisposable databaseUpdateDisposable = new CompositeDisposable(); + // This is the only listener we need for thumbnail loading, since there is always at most only + // one thumbnail being loaded at a time. This field is also here to maintain a strong reference, + // which would otherwise be garbage collected since Picasso holds weak references to targets. + @NonNull private final Target currentThumbnailTarget; + /*////////////////////////////////////////////////////////////////////////// // Utils //////////////////////////////////////////////////////////////////////////*/ @@ -263,6 +269,8 @@ public final class Player implements PlaybackListener, Listener { videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver()); audioResolver = new AudioPlaybackResolver(context, dataSource); + currentThumbnailTarget = getCurrentThumbnailTarget(); + // The UIs added here should always be present. They will be initialized when the player // reaches the initialization step. Make sure the media session ui is before the // notification ui in the UIs list, since the notification depends on the media session in @@ -573,7 +581,7 @@ public final class Player implements PlaybackListener, Listener { databaseUpdateDisposable.clear(); progressUpdateDisposable.set(null); - PicassoHelper.cancelTag(PicassoHelper.PLAYER_THUMBNAIL_TAG); // cancel thumbnail loading + cancelLoadingCurrentThumbnail(); UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object } @@ -747,12 +755,47 @@ public final class Player implements PlaybackListener, Listener { //////////////////////////////////////////////////////////////////////////*/ //region Thumbnail loading + private Target getCurrentThumbnailTarget() { + // a Picasso target is just a listener for thumbnail loading events + return new Target() { + @Override + public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: bitmap = [" + bitmap + + " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = [" + + from + "]"); + } + currentThumbnail = bitmap; + // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. + UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); + } + + @Override + public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { + Log.e(TAG, "Thumbnail - onBitmapFailed() called", e); + currentThumbnail = null; + // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too. + UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); + } + + @Override + public void onPrepareLoad(final Drawable placeHolderDrawable) { + if (DEBUG) { + Log.d(TAG, "Thumbnail - onPrepareLoad() called"); + } + } + }; + } + private void loadCurrentThumbnail(final String url) { if (DEBUG) { Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with url = [" + (url == null ? "null" : url) + "]"); } + // first cancel any previous loading + cancelLoadingCurrentThumbnail(); + // Unset currentThumbnail, since it is now outdated. This ensures it is not used in media // session metadata while the new thumbnail is being loaded by Picasso. currentThumbnail = null; @@ -761,34 +804,14 @@ public final class Player implements PlaybackListener, Listener { } // scale down the notification thumbnail for performance - PicassoHelper.loadScaledDownThumbnail(context, url).into(new Target() { - @Override - public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: url = [" + url - + "], " + "bitmap = [" + bitmap + " -> " + bitmap.getWidth() + "x" - + bitmap.getHeight() + "], from = [" + from + "]"); - } + PicassoHelper.loadScaledDownThumbnail(context, url) + .tag(PICASSO_PLAYER_THUMBNAIL_TAG) + .into(currentThumbnailTarget); + } - currentThumbnail = bitmap; - // there is a new thumbnail, so changed the end screen thumbnail, too. - UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap)); - } - - @Override - public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { - Log.e(TAG, "Thumbnail - onBitmapFailed() called: url = [" + url + "]", e); - currentThumbnail = null; - UIs.call(playerUi -> playerUi.onThumbnailLoaded(null)); - } - - @Override - public void onPrepareLoad(final Drawable placeHolderDrawable) { - if (DEBUG) { - Log.d(TAG, "Thumbnail - onPrepareLoad() called: url = [" + url + "]"); - } - } - }); + private void cancelLoadingCurrentThumbnail() { + // cancel the Picasso job associated with the player thumbnail, if any + PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG); } //endregion diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index 5739b930b..2e781631e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -27,7 +27,6 @@ import okhttp3.OkHttpClient; public final class PicassoHelper { private static final String TAG = PicassoHelper.class.getSimpleName(); - public static final String PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG"; private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY = "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"; @@ -128,7 +127,6 @@ public final class PicassoHelper { public static RequestCreator loadScaledDownThumbnail(final Context context, final String url) { // scale down the notification thumbnail for performance return PicassoHelper.loadThumbnail(url) - .tag(PLAYER_THUMBNAIL_TAG) .transform(new Transformation() { @Override public Bitmap transform(final Bitmap source) { From 973a9660115cc809a99ae571af45f0b67a9c7c03 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 26 Jul 2022 16:35:57 +0200 Subject: [PATCH 304/992] Review suggestions --- .../newpipe/player/mediasession/PlayQueueNavigator.java | 2 +- .../newpipe/player/notification/NotificationUtil.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index 7bd27bfdc..e84c0837b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -126,7 +126,7 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator .setTitle(item.getTitle()) .setSubtitle(item.getUploader()); - // set additional metadata for A2DP/AVRCP + // set additional metadata for A2DP/AVRCP (Audio/Video Bluetooth profiles) final Bundle additionalMetadata = new Bundle(); additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, item.getTitle()); additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); diff --git a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java index 84e9cc3bc..2c3199a28 100644 --- a/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java +++ b/app/src/main/java/org/schabi/newpipe/player/notification/NotificationUtil.java @@ -105,8 +105,10 @@ public final class NotificationUtil { final int[] compactSlots = compactSlotList.stream().mapToInt(Integer::intValue).toArray(); final MediaStyle mediaStyle = new MediaStyle().setShowActionsInCompactView(compactSlots); - player.UIs().get(MediaSessionPlayerUi.class).flatMap(MediaSessionPlayerUi::getSessionToken) - .ifPresent(mediaStyle::setMediaSession); + player.UIs() + .get(MediaSessionPlayerUi.class) + .flatMap(MediaSessionPlayerUi::getSessionToken) + .ifPresent(mediaStyle::setMediaSession); builder.setStyle(mediaStyle) .setPriority(NotificationCompat.PRIORITY_HIGH) From 59d1ded94e876bd040a7c23a7a2e3ae18987df07 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Thu, 25 Aug 2022 16:59:46 +0200 Subject: [PATCH 305/992] Fixed sonar detected problems + Automatically fixed code style (imports) --- .../java/org/schabi/newpipe/player/Player.java | 2 +- .../mediasession/MediaSessionPlayerUi.java | 6 ------ .../mediasession/PlayQueueNavigator.java | 18 +++++++++--------- 3 files changed, 10 insertions(+), 16 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 45b9b0fde..319c163e8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -222,7 +222,7 @@ public final class Player implements PlaybackListener, Listener { // UIs, listeners and disposables //////////////////////////////////////////////////////////////////////////*/ - @SuppressWarnings("MemberName") // keep the unusual member name + @SuppressWarnings({"MemberName", "java:S116"}) // keep the unusual member name private final PlayerUiList UIs; private BroadcastReceiver broadcastReceiver; 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 92a137900..e9541ab06 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 @@ -64,12 +64,6 @@ public class MediaSessionPlayerUi extends PlayerUi { } } - @Override - public void onBroadcastReceived(final Intent intent) { - super.onBroadcastReceived(intent); - // TODO decide whether to handle ACTION_HEADSET_PLUG or not - } - @Override public void onThumbnailLoaded(@Nullable final Bitmap bitmap) { super.onThumbnailLoaded(bitmap); diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java index e84c0837b..2e54b1129 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -1,5 +1,9 @@ package org.schabi.newpipe.player.mediasession; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; @@ -13,19 +17,15 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.util.Util; +import org.schabi.newpipe.player.Player; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueueItem; + import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; -import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; - -import org.schabi.newpipe.player.Player; -import org.schabi.newpipe.player.playqueue.PlayQueue; -import org.schabi.newpipe.player.playqueue.PlayQueueItem; - public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator { private static final int MAX_QUEUE_SIZE = 10; @@ -132,7 +132,7 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator additionalMetadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, item.getUploader()); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, item.getDuration() * 1000); - additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1); + additionalMetadata.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, index + 1L); additionalMetadata .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, player.getPlayQueue().size()); descBuilder.setExtras(additionalMetadata); From 6805c75c9cf90f9448e266eda34866136f0c71c8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 2 Aug 2022 14:46:11 +0200 Subject: [PATCH 306/992] Fix surface view not resizing video correctly Also fix yet another random null pointer exception that could happen when adding the video player view --- .../fragments/detail/VideoDetailFragment.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 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 3b1bdaede..594006ab5 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 @@ -1220,7 +1220,7 @@ public final class VideoDetailFragment } final PlayQueue queue = setupPlayQueueForIntent(false); - addVideoPlayerView(); + tryAddVideoPlayerView(); final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(), PlayerService.class, queue, true, autoPlayEnabled); @@ -1301,21 +1301,27 @@ public final class VideoDetailFragment && PlayerHelper.isAutoplayAllowedByUser(requireContext()); } - private void addVideoPlayerView() { - if (!isPlayerAvailable() || getView() == null) { - return; - } - setHeightThumbnail(); + private void tryAddVideoPlayerView() { + // do all the null checks in the posted lambda, since the player, the binding and the view + // could be set or unset before the lambda gets executed on the next main thread cycle + new Handler(Looper.getMainLooper()).post(() -> { + if (!isPlayerAvailable() || getView() == null) { + return; + } - // Prevent from re-adding a view multiple times - new Handler(Looper.getMainLooper()).post(() -> - player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { - if (binding != null) { - playerUi.removeViewFromParent(); - binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); - playerUi.setupVideoSurfaceIfNeeded(); - } - })); + // setup the surface view height, so that it fits the video correctly + setHeightThumbnail(); + + player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> { + // sometimes binding would be null here, even though getView() != null above u.u + if (binding != null) { + // prevent from re-adding a view multiple times + playerUi.removeViewFromParent(); + binding.playerPlaceholder.addView(playerUi.getBinding().getRoot()); + playerUi.setupVideoSurfaceIfNeeded(); + } + }); + }); } private void removeVideoPlayerView() { @@ -1784,7 +1790,7 @@ public final class VideoDetailFragment @Override public void onViewCreated() { - addVideoPlayerView(); + tryAddVideoPlayerView(); } @Override @@ -1926,7 +1932,7 @@ public final class VideoDetailFragment } scrollToTop(); - addVideoPlayerView(); + tryAddVideoPlayerView(); } @Override From 500acce178d1e9f21d811f7e9f3b3bfac6c4aeb0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 24 Aug 2022 15:08:24 +0200 Subject: [PATCH 307/992] Fix regression in screen rotation animation --- .../newpipe/fragments/detail/VideoDetailFragment.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 594006ab5..9800b2b0a 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 @@ -1302,8 +1302,14 @@ public final class VideoDetailFragment } private void tryAddVideoPlayerView() { - // do all the null checks in the posted lambda, since the player, the binding and the view - // could be set or unset before the lambda gets executed on the next main thread cycle + if (isPlayerAvailable() && getView() != null) { + // Setup the surface view height, so that it fits the video correctly; this is done also + // here, and not only in the Handler, to avoid a choppy fullscreen rotation animation. + setHeightThumbnail(); + } + + // do all the null checks in the posted lambda, too, since the player, the binding and the + // view could be set or unset before the lambda gets executed on the next main thread cycle new Handler(Looper.getMainLooper()).post(() -> { if (!isPlayerAvailable() || getView() == null) { return; From ca0f56eea815bd2112f2e36655c4f753dd975a70 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 24 Aug 2022 16:03:15 +0200 Subject: [PATCH 308/992] Avoid setting invalid states to bottom sheet callback --- .../fragments/detail/VideoDetailFragment.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 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 9800b2b0a..0ec1efe57 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 @@ -180,6 +180,8 @@ public final class VideoDetailFragment @State int bottomSheetState = BottomSheetBehavior.STATE_EXPANDED; @State + int lastStableBottomSheetState = BottomSheetBehavior.STATE_EXPANDED; + @State protected boolean autoPlayEnabled = true; @Nullable @@ -269,7 +271,7 @@ public final class VideoDetailFragment public static VideoDetailFragment getInstanceInCollapsedState() { final VideoDetailFragment instance = new VideoDetailFragment(); - instance.bottomSheetState = BottomSheetBehavior.STATE_COLLAPSED; + instance.updateBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED); return instance; } @@ -1170,7 +1172,7 @@ public final class VideoDetailFragment // doesn't tell which state it was settling to, and thus the bottom sheet settles to // STATE_COLLAPSED. This can be solved by manually setting the state that will be // restored (i.e. bottomSheetState) to STATE_EXPANDED. - bottomSheetState = BottomSheetBehavior.STATE_EXPANDED; + updateBottomSheetState(BottomSheetBehavior.STATE_EXPANDED); // toggle landscape in order to open directly in fullscreen onScreenRotationButtonClicked(); } @@ -2284,7 +2286,9 @@ public final class VideoDetailFragment final FrameLayout bottomSheetLayout = activity.findViewById(R.id.fragment_player_holder); bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetLayout); - bottomSheetBehavior.setState(bottomSheetState); + bottomSheetBehavior.setState(lastStableBottomSheetState); + updateBottomSheetState(lastStableBottomSheetState); + final int peekHeight = getResources().getDimensionPixelSize(R.dimen.mini_player_height); if (bottomSheetState != BottomSheetBehavior.STATE_HIDDEN) { manageSpaceAtTheBottom(false); @@ -2300,7 +2304,7 @@ public final class VideoDetailFragment bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull final View bottomSheet, final int newState) { - bottomSheetState = newState; + updateBottomSheetState(newState); switch (newState) { case BottomSheetBehavior.STATE_HIDDEN: @@ -2441,4 +2445,12 @@ public final class VideoDetailFragment return player.UIs().get(VideoPlayerUi.class) .map(playerUi -> playerUi.getBinding().getRoot()); } + + private void updateBottomSheetState(final int newState) { + bottomSheetState = newState; + if (newState != BottomSheetBehavior.STATE_DRAGGING + && newState != BottomSheetBehavior.STATE_SETTLING) { + lastStableBottomSheetState = newState; + } + } } From f9994abb94453f97080bdd48200c6009b965a43c Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 24 Aug 2022 17:48:02 +0200 Subject: [PATCH 309/992] Prevent tapping on thumbnail if video details are not loaded --- .../fragments/detail/VideoDetailFragment.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 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 0ec1efe57..09e085791 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 @@ -505,12 +505,18 @@ public final class VideoDetailFragment } break; case R.id.detail_thumbnail_root_layout: - autoPlayEnabled = true; // forcefully start playing - // FIXME Workaround #7427 - if (isPlayerAvailable()) { - player.setRecovery(); + // make sure not to open any player if there is nothing currently loaded! + // FIXME removing this `if` causes the player service to start correctly, then stop, + // then restart badly without calling `startForeground()`, causing a crash when + // later closing the detail fragment + if (currentInfo != null) { + autoPlayEnabled = true; // forcefully start playing + // FIXME Workaround #7427 + if (isPlayerAvailable()) { + player.setRecovery(); + } + openVideoPlayerAutoFullscreen(); } - openVideoPlayerAutoFullscreen(); break; case R.id.detail_title_root_layout: toggleTitleAndSecondaryControls(); From 67b5de38b1cdb88a160b34ec18a0ae2e4248ce41 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 27 Aug 2022 11:53:53 +0200 Subject: [PATCH 310/992] Translated using Weblate (Chinese (Simplified)) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (70 of 70 strings) Translated using Weblate (Czech) Currently translated at 100.0% (70 of 70 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (70 of 70 strings) Translated using Weblate (Azerbaijani) Currently translated at 100.0% (69 of 69 strings) Translated using Weblate (Interlingua) Currently translated at 35.0% (224 of 640 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 62.3% (43 of 69 strings) Translated using Weblate (Korean) Currently translated at 10.1% (7 of 69 strings) Translated using Weblate (French) Currently translated at 89.8% (62 of 69 strings) Translated using Weblate (Hebrew) Currently translated at 55.0% (38 of 69 strings) Translated using Weblate (Czech) Currently translated at 100.0% (69 of 69 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (69 of 69 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 13.0% (9 of 69 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Bengali (Bangladesh)) Currently translated at 5.8% (4 of 68 strings) Translated using Weblate (French) Currently translated at 89.7% (61 of 68 strings) Translated using Weblate (French) Currently translated at 89.7% (61 of 68 strings) Translated using Weblate (Bengali) Currently translated at 22.0% (15 of 68 strings) Translated using Weblate (Azerbaijani) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 73.5% (50 of 68 strings) Translated using Weblate (Russian) Currently translated at 30.8% (21 of 68 strings) Translated using Weblate (German) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hungarian) Currently translated at 7.3% (5 of 68 strings) Translated using Weblate (Croatian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Czech) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hungarian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Polish) Currently translated at 58.8% (40 of 68 strings) Translated using Weblate (Bengali) Currently translated at 88.7% (568 of 640 strings) Translated using Weblate (Malayalam) Currently translated at 90.7% (581 of 640 strings) Translated using Weblate (Interlingua) Currently translated at 33.5% (215 of 640 strings) Translated using Weblate (Croatian) Currently translated at 98.1% (628 of 640 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hungarian) Currently translated at 93.9% (601 of 640 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Somali) Currently translated at 89.0% (570 of 640 strings) Translated using Weblate (German) Currently translated at 66.1% (45 of 68 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Armenian) Currently translated at 29.2% (187 of 640 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Spanish) Currently translated at 99.5% (637 of 640 strings) Translated using Weblate (Urdu) Currently translated at 67.1% (430 of 640 strings) Translated using Weblate (Croatian) Currently translated at 97.5% (624 of 640 strings) Translated using Weblate (Portuguese) Currently translated at 61.7% (42 of 68 strings) Translated using Weblate (Russian) Currently translated at 22.0% (15 of 68 strings) Translated using Weblate (Russian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 72.0% (49 of 68 strings) Translated using Weblate (Italian) Currently translated at 41.1% (28 of 68 strings) Translated using Weblate (Czech) Currently translated at 100.0% (68 of 68 strings) Translated using Weblate (Portuguese (Portugal)) Currently translated at 99.6% (638 of 640 strings) Translated using Weblate (Sardinian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Galician) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Estonian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Azerbaijani) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Bulgarian) Currently translated at 72.0% (461 of 640 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Hebrew) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Bengali (Bangladesh)) Currently translated at 64.6% (414 of 640 strings) Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Persian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Polish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Czech) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Greek) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Basque) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Italian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (German) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Italian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Slovak) Currently translated at 8.8% (6 of 68 strings) Translated using Weblate (Slovak) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (French) Currently translated at 70.5% (48 of 68 strings) Translated using Weblate (Filipino) Currently translated at 36.8% (236 of 640 strings) Translated using Weblate (Bulgarian) Currently translated at 70.6% (452 of 640 strings) Translated using Weblate (Russian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Dutch) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 32.3% (22 of 68 strings) Translated using Weblate (Telugu) Currently translated at 65.6% (420 of 640 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Romanian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 97.1% (622 of 640 strings) Translated using Weblate (Italian) Currently translated at 100.0% (640 of 640 strings) Translated using Weblate (Japanese) Currently translated at 99.2% (635 of 640 strings) Co-authored-by: Agnieszka C Co-authored-by: Ajeje Brazorf Co-authored-by: Alex25820 Co-authored-by: Danial Behzadi Co-authored-by: Davit Mayilyan Co-authored-by: Edward Co-authored-by: Emin Tufan Çetin Co-authored-by: Eric Co-authored-by: Evghenii Botnari Co-authored-by: Fjuro Co-authored-by: Francisco Ruiz Co-authored-by: Giovanni Donisi Co-authored-by: GnuPGを使うべきだ Co-authored-by: Gontzal Manuel Pujana Onaindia Co-authored-by: Hin Weisner Co-authored-by: Hosted Weblate Co-authored-by: Igor Sorocean Co-authored-by: Ihor Hordiichuk Co-authored-by: JScocktail Co-authored-by: Jalaluddin Co-authored-by: Jeff Huang Co-authored-by: Josu Co-authored-by: Karl Tammik Co-authored-by: Laura Vasconcelos Pereira Felippe Co-authored-by: Lenn Art Co-authored-by: Linerly Co-authored-by: Louis V Co-authored-by: Marian Hanzel Co-authored-by: MatthieuPh Co-authored-by: Milo Ivir Co-authored-by: Mohammed Anas Co-authored-by: MΛX Co-authored-by: Nadir Nour Co-authored-by: Napstaguy04 Co-authored-by: Nizami Co-authored-by: Oymate Co-authored-by: Pieter van der Razemond Co-authored-by: Ray Co-authored-by: Rex_sa Co-authored-by: Ricardo Co-authored-by: S3aBreeze Co-authored-by: SC Co-authored-by: STV Co-authored-by: Samar Ali Co-authored-by: Santhosh J Co-authored-by: Software In Interlingua Co-authored-by: TXRdev Archive Co-authored-by: Tadeusz Dudek Co-authored-by: ThePlanetaryDroid Co-authored-by: Translator Co-authored-by: Vasilis K Co-authored-by: VfBFan Co-authored-by: Viktor Co-authored-by: WB Co-authored-by: Xəyyam Qocayev Co-authored-by: Yaron Shahrabani Co-authored-by: Zoldtukor Co-authored-by: chr56 Co-authored-by: i-am-SangWoo-Lee Co-authored-by: nautilusx Co-authored-by: pjammo Co-authored-by: rakijagamer-2003 Co-authored-by: remon-drk Co-authored-by: ssantos Co-authored-by: subba raidu Co-authored-by: yunna Co-authored-by: Симеон Цветков Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/az/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/bn_BD/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/cs/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/de/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/fr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/he/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/hu/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/it/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ko/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pl/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/pt/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/ru/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/sk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/tr/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/uk/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant/ Translate-URL: https://hosted.weblate.org/projects/newpipe/metadata/zh_Hant_HK/ Translation: NewPipe/Metadata --- app/src/main/res/values-ar/strings.xml | 11 +- app/src/main/res/values-az/strings.xml | 7 +- app/src/main/res/values-bg/strings.xml | 15 +- app/src/main/res/values-bn-rBD/strings.xml | 1 + app/src/main/res/values-bn/strings.xml | 2 + app/src/main/res/values-cs/strings.xml | 49 +- app/src/main/res/values-de/strings.xml | 9 +- app/src/main/res/values-el/strings.xml | 7 +- app/src/main/res/values-es/strings.xml | 12 +- app/src/main/res/values-et/strings.xml | 6 +- app/src/main/res/values-eu/strings.xml | 13 +- app/src/main/res/values-fa/strings.xml | 6 +- app/src/main/res/values-fil/strings.xml | 7 +- app/src/main/res/values-fr/strings.xml | 15 +- app/src/main/res/values-gl/strings.xml | 13 +- app/src/main/res/values-he/strings.xml | 6 +- app/src/main/res/values-hr/strings.xml | 494 +++++++++--------- app/src/main/res/values-hu/strings.xml | 41 +- app/src/main/res/values-hy/strings.xml | 3 + app/src/main/res/values-ia/strings.xml | 12 + app/src/main/res/values-in/strings.xml | 5 +- app/src/main/res/values-it/strings.xml | 7 +- app/src/main/res/values-ja/strings.xml | 83 +-- app/src/main/res/values-ml/strings.xml | 4 +- app/src/main/res/values-nl/strings.xml | 8 + app/src/main/res/values-pl/strings.xml | 5 +- app/src/main/res/values-pt-rBR/strings.xml | 5 +- app/src/main/res/values-pt-rPT/strings.xml | 2 + app/src/main/res/values-pt/strings.xml | 6 +- app/src/main/res/values-ro/strings.xml | 13 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-sc/strings.xml | 6 +- app/src/main/res/values-sk/strings.xml | 8 + app/src/main/res/values-so/strings.xml | 4 +- app/src/main/res/values-sv/strings.xml | 9 +- app/src/main/res/values-te/strings.xml | 25 + app/src/main/res/values-tr/strings.xml | 37 +- app/src/main/res/values-uk/strings.xml | 5 +- app/src/main/res/values-ur/strings.xml | 6 +- app/src/main/res/values-vi/strings.xml | 95 ++-- app/src/main/res/values-zh-rCN/strings.xml | 6 +- app/src/main/res/values-zh-rHK/strings.xml | 61 ++- app/src/main/res/values-zh-rTW/strings.xml | 5 +- .../metadata/android/az/changelogs/988.txt | 2 + .../metadata/android/bn/changelogs/66.txt | 33 ++ .../metadata/android/bn/changelogs/730.txt | 4 +- .../metadata/android/bn/changelogs/770.txt | 2 +- .../metadata/android/bn/changelogs/956.txt | 2 +- .../metadata/android/bn/short_description.txt | 2 +- .../metadata/android/bn_BD/changelogs/63.txt | 8 + .../metadata/android/bn_BD/changelogs/64.txt | 8 + .../android/bn_BD/full_description.txt | 1 + .../android/bn_BD/short_description.txt | 1 + .../metadata/android/cs/changelogs/63.txt | 8 + .../metadata/android/cs/changelogs/64.txt | 8 + .../metadata/android/cs/changelogs/65.txt | 26 + .../metadata/android/cs/changelogs/66.txt | 16 + .../metadata/android/cs/changelogs/68.txt | 15 + .../metadata/android/cs/changelogs/69.txt | 9 + .../metadata/android/cs/changelogs/70.txt | 7 + .../metadata/android/cs/changelogs/71.txt | 7 + .../metadata/android/cs/changelogs/730.txt | 2 + .../metadata/android/cs/changelogs/740.txt | 23 + .../metadata/android/cs/changelogs/750.txt | 14 + .../metadata/android/cs/changelogs/760.txt | 14 + .../metadata/android/cs/changelogs/770.txt | 4 + .../metadata/android/cs/changelogs/780.txt | 11 + .../metadata/android/cs/changelogs/790.txt | 9 + .../metadata/android/cs/changelogs/800.txt | 10 + .../metadata/android/cs/changelogs/810.txt | 8 + .../metadata/android/cs/changelogs/820.txt | 1 + .../metadata/android/cs/changelogs/830.txt | 1 + .../metadata/android/cs/changelogs/840.txt | 8 + .../metadata/android/cs/changelogs/850.txt | 1 + .../metadata/android/cs/changelogs/860.txt | 7 + .../metadata/android/cs/changelogs/870.txt | 2 + .../metadata/android/cs/changelogs/900.txt | 13 + .../metadata/android/cs/changelogs/910.txt | 1 + .../metadata/android/cs/changelogs/920.txt | 9 + .../metadata/android/cs/changelogs/930.txt | 10 + .../metadata/android/cs/changelogs/940.txt | 9 + .../metadata/android/cs/changelogs/950.txt | 4 + .../metadata/android/cs/changelogs/951.txt | 6 + .../metadata/android/cs/changelogs/953.txt | 1 + .../metadata/android/cs/changelogs/954.txt | 6 + .../metadata/android/cs/changelogs/955.txt | 3 + .../metadata/android/cs/changelogs/956.txt | 1 + .../metadata/android/cs/changelogs/957.txt | 8 + .../metadata/android/cs/changelogs/958.txt | 15 + .../metadata/android/cs/changelogs/959.txt | 3 + .../metadata/android/cs/changelogs/960.txt | 4 + .../metadata/android/cs/changelogs/961.txt | 12 + .../metadata/android/cs/changelogs/963.txt | 1 + .../metadata/android/cs/changelogs/964.txt | 8 + .../metadata/android/cs/changelogs/965.txt | 6 + .../metadata/android/cs/changelogs/966.txt | 14 + .../metadata/android/cs/changelogs/967.txt | 1 + .../metadata/android/cs/changelogs/968.txt | 7 + .../metadata/android/cs/changelogs/969.txt | 8 + .../metadata/android/cs/changelogs/970.txt | 11 + .../metadata/android/cs/changelogs/971.txt | 3 + .../metadata/android/cs/changelogs/972.txt | 14 + .../metadata/android/cs/changelogs/973.txt | 4 + .../metadata/android/cs/changelogs/974.txt | 5 + .../metadata/android/cs/changelogs/975.txt | 17 + .../metadata/android/cs/changelogs/976.txt | 10 + .../metadata/android/cs/changelogs/977.txt | 10 + .../metadata/android/cs/changelogs/978.txt | 1 + .../metadata/android/cs/changelogs/979.txt | 2 + .../metadata/android/cs/changelogs/980.txt | 13 + .../metadata/android/cs/changelogs/981.txt | 2 + .../metadata/android/cs/changelogs/982.txt | 1 + .../metadata/android/cs/changelogs/983.txt | 9 + .../metadata/android/cs/changelogs/984.txt | 7 + .../metadata/android/cs/changelogs/985.txt | 1 + .../metadata/android/cs/changelogs/986.txt | 16 + .../metadata/android/cs/changelogs/987.txt | 12 + .../metadata/android/cs/changelogs/988.txt | 2 + .../metadata/android/cs/changelogs/989.txt | 3 + .../metadata/android/de/changelogs/987.txt | 2 +- .../metadata/android/fr/changelogs/63.txt | 4 +- .../metadata/android/fr/changelogs/65.txt | 26 + .../metadata/android/fr/changelogs/66.txt | 28 + .../metadata/android/fr/changelogs/68.txt | 31 ++ .../metadata/android/fr/changelogs/69.txt | 19 + .../metadata/android/fr/changelogs/70.txt | 25 + .../metadata/android/fr/changelogs/71.txt | 14 +- .../metadata/android/fr/changelogs/964.txt | 8 + .../metadata/android/fr/changelogs/966.txt | 14 + .../metadata/android/fr/changelogs/969.txt | 8 + .../metadata/android/fr/changelogs/970.txt | 11 + .../metadata/android/fr/changelogs/971.txt | 3 + .../metadata/android/fr/changelogs/973.txt | 4 + .../metadata/android/fr/changelogs/974.txt | 5 + .../metadata/android/fr/changelogs/977.txt | 8 + .../metadata/android/fr/changelogs/980.txt | 13 + .../metadata/android/fr/changelogs/987.txt | 6 +- .../metadata/android/fr/changelogs/988.txt | 2 + .../metadata/android/fr/short_description.txt | 2 +- .../metadata/android/he/changelogs/988.txt | 2 + .../metadata/android/hu/changelogs/65.txt | 26 + .../metadata/android/hu/short_description.txt | 2 +- .../metadata/android/it/changelogs/63.txt | 10 +- .../metadata/android/it/changelogs/730.txt | 2 + .../metadata/android/ko/changelogs/63.txt | 8 + .../metadata/android/ko/changelogs/64.txt | 8 + .../metadata/android/pl/changelogs/964.txt | 8 + .../metadata/android/pl/changelogs/965.txt | 5 + .../metadata/android/pt/changelogs/955.txt | 6 +- .../metadata/android/ru/changelogs/65.txt | 1 + .../metadata/android/ru/changelogs/66.txt | 1 + .../metadata/android/ru/changelogs/68.txt | 1 + .../metadata/android/ru/changelogs/69.txt | 1 + .../metadata/android/ru/changelogs/70.txt | 1 + .../metadata/android/ru/changelogs/780.txt | 12 + .../metadata/android/ru/changelogs/790.txt | 1 + .../metadata/android/ru/changelogs/985.txt | 1 + .../metadata/android/ru/changelogs/987.txt | 12 + .../metadata/android/sk/changelogs/987.txt | 12 + .../metadata/android/tr/changelogs/63.txt | 4 +- .../metadata/android/tr/full_description.txt | 3 +- .../metadata/android/uk/changelogs/988.txt | 2 + .../metadata/android/uk/changelogs/989.txt | 3 + .../android/zh-Hans/changelogs/988.txt | 2 + .../android/zh-Hans/changelogs/989.txt | 3 + .../android/zh-Hant/changelogs/988.txt | 2 + .../android/zh_Hant_HK/changelogs/988.txt | 2 + 167 files changed, 1613 insertions(+), 466 deletions(-) create mode 100644 fastlane/metadata/android/az/changelogs/988.txt create mode 100644 fastlane/metadata/android/bn/changelogs/66.txt create mode 100644 fastlane/metadata/android/bn_BD/changelogs/63.txt create mode 100644 fastlane/metadata/android/bn_BD/changelogs/64.txt create mode 100644 fastlane/metadata/android/bn_BD/full_description.txt create mode 100644 fastlane/metadata/android/bn_BD/short_description.txt create mode 100644 fastlane/metadata/android/cs/changelogs/63.txt create mode 100644 fastlane/metadata/android/cs/changelogs/64.txt create mode 100644 fastlane/metadata/android/cs/changelogs/65.txt create mode 100644 fastlane/metadata/android/cs/changelogs/66.txt create mode 100644 fastlane/metadata/android/cs/changelogs/68.txt create mode 100644 fastlane/metadata/android/cs/changelogs/69.txt create mode 100644 fastlane/metadata/android/cs/changelogs/70.txt create mode 100644 fastlane/metadata/android/cs/changelogs/71.txt create mode 100644 fastlane/metadata/android/cs/changelogs/730.txt create mode 100644 fastlane/metadata/android/cs/changelogs/740.txt create mode 100644 fastlane/metadata/android/cs/changelogs/750.txt create mode 100644 fastlane/metadata/android/cs/changelogs/760.txt create mode 100644 fastlane/metadata/android/cs/changelogs/770.txt create mode 100644 fastlane/metadata/android/cs/changelogs/780.txt create mode 100644 fastlane/metadata/android/cs/changelogs/790.txt create mode 100644 fastlane/metadata/android/cs/changelogs/800.txt create mode 100644 fastlane/metadata/android/cs/changelogs/810.txt create mode 100644 fastlane/metadata/android/cs/changelogs/820.txt create mode 100644 fastlane/metadata/android/cs/changelogs/830.txt create mode 100644 fastlane/metadata/android/cs/changelogs/840.txt create mode 100644 fastlane/metadata/android/cs/changelogs/850.txt create mode 100644 fastlane/metadata/android/cs/changelogs/860.txt create mode 100644 fastlane/metadata/android/cs/changelogs/870.txt create mode 100644 fastlane/metadata/android/cs/changelogs/900.txt create mode 100644 fastlane/metadata/android/cs/changelogs/910.txt create mode 100644 fastlane/metadata/android/cs/changelogs/920.txt create mode 100644 fastlane/metadata/android/cs/changelogs/930.txt create mode 100644 fastlane/metadata/android/cs/changelogs/940.txt create mode 100644 fastlane/metadata/android/cs/changelogs/950.txt create mode 100644 fastlane/metadata/android/cs/changelogs/951.txt create mode 100644 fastlane/metadata/android/cs/changelogs/953.txt create mode 100644 fastlane/metadata/android/cs/changelogs/954.txt create mode 100644 fastlane/metadata/android/cs/changelogs/955.txt create mode 100644 fastlane/metadata/android/cs/changelogs/956.txt create mode 100644 fastlane/metadata/android/cs/changelogs/957.txt create mode 100644 fastlane/metadata/android/cs/changelogs/958.txt create mode 100644 fastlane/metadata/android/cs/changelogs/959.txt create mode 100644 fastlane/metadata/android/cs/changelogs/960.txt create mode 100644 fastlane/metadata/android/cs/changelogs/961.txt create mode 100644 fastlane/metadata/android/cs/changelogs/963.txt create mode 100644 fastlane/metadata/android/cs/changelogs/964.txt create mode 100644 fastlane/metadata/android/cs/changelogs/965.txt create mode 100644 fastlane/metadata/android/cs/changelogs/966.txt create mode 100644 fastlane/metadata/android/cs/changelogs/967.txt create mode 100644 fastlane/metadata/android/cs/changelogs/968.txt create mode 100644 fastlane/metadata/android/cs/changelogs/969.txt create mode 100644 fastlane/metadata/android/cs/changelogs/970.txt create mode 100644 fastlane/metadata/android/cs/changelogs/971.txt create mode 100644 fastlane/metadata/android/cs/changelogs/972.txt create mode 100644 fastlane/metadata/android/cs/changelogs/973.txt create mode 100644 fastlane/metadata/android/cs/changelogs/974.txt create mode 100644 fastlane/metadata/android/cs/changelogs/975.txt create mode 100644 fastlane/metadata/android/cs/changelogs/976.txt create mode 100644 fastlane/metadata/android/cs/changelogs/977.txt create mode 100644 fastlane/metadata/android/cs/changelogs/978.txt create mode 100644 fastlane/metadata/android/cs/changelogs/979.txt create mode 100644 fastlane/metadata/android/cs/changelogs/980.txt create mode 100644 fastlane/metadata/android/cs/changelogs/981.txt create mode 100644 fastlane/metadata/android/cs/changelogs/982.txt create mode 100644 fastlane/metadata/android/cs/changelogs/983.txt create mode 100644 fastlane/metadata/android/cs/changelogs/984.txt create mode 100644 fastlane/metadata/android/cs/changelogs/985.txt create mode 100644 fastlane/metadata/android/cs/changelogs/986.txt create mode 100644 fastlane/metadata/android/cs/changelogs/987.txt create mode 100644 fastlane/metadata/android/cs/changelogs/988.txt create mode 100644 fastlane/metadata/android/cs/changelogs/989.txt create mode 100644 fastlane/metadata/android/fr/changelogs/65.txt create mode 100644 fastlane/metadata/android/fr/changelogs/66.txt create mode 100644 fastlane/metadata/android/fr/changelogs/68.txt create mode 100644 fastlane/metadata/android/fr/changelogs/69.txt create mode 100644 fastlane/metadata/android/fr/changelogs/70.txt create mode 100644 fastlane/metadata/android/fr/changelogs/964.txt create mode 100644 fastlane/metadata/android/fr/changelogs/966.txt create mode 100644 fastlane/metadata/android/fr/changelogs/969.txt create mode 100644 fastlane/metadata/android/fr/changelogs/970.txt create mode 100644 fastlane/metadata/android/fr/changelogs/971.txt create mode 100644 fastlane/metadata/android/fr/changelogs/973.txt create mode 100644 fastlane/metadata/android/fr/changelogs/974.txt create mode 100644 fastlane/metadata/android/fr/changelogs/977.txt create mode 100644 fastlane/metadata/android/fr/changelogs/980.txt create mode 100644 fastlane/metadata/android/fr/changelogs/988.txt create mode 100644 fastlane/metadata/android/he/changelogs/988.txt create mode 100644 fastlane/metadata/android/hu/changelogs/65.txt create mode 100644 fastlane/metadata/android/it/changelogs/730.txt create mode 100644 fastlane/metadata/android/ko/changelogs/63.txt create mode 100644 fastlane/metadata/android/ko/changelogs/64.txt create mode 100644 fastlane/metadata/android/pl/changelogs/964.txt create mode 100644 fastlane/metadata/android/pl/changelogs/965.txt create mode 100644 fastlane/metadata/android/ru/changelogs/65.txt create mode 100644 fastlane/metadata/android/ru/changelogs/66.txt create mode 100644 fastlane/metadata/android/ru/changelogs/68.txt create mode 100644 fastlane/metadata/android/ru/changelogs/69.txt create mode 100644 fastlane/metadata/android/ru/changelogs/70.txt create mode 100644 fastlane/metadata/android/ru/changelogs/780.txt create mode 100644 fastlane/metadata/android/ru/changelogs/790.txt create mode 100644 fastlane/metadata/android/ru/changelogs/985.txt create mode 100644 fastlane/metadata/android/ru/changelogs/987.txt create mode 100644 fastlane/metadata/android/sk/changelogs/987.txt create mode 100644 fastlane/metadata/android/uk/changelogs/988.txt create mode 100644 fastlane/metadata/android/uk/changelogs/989.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/988.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/989.txt create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/988.txt create mode 100644 fastlane/metadata/android/zh_Hant_HK/changelogs/988.txt diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 71d849a2f..700a612b8 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -200,7 +200,7 @@ © %1$sبواسطة%2$sتحت%3$s صفحة الكشك حدد كشك - ابدأ التشغيل في الخلفية + بدأ التشغيل في الخلفية المحتوى الإفتراضي حسب البلد الإنتقال إلى التشغيل في الخلفية الإنتقال إلى التشغيل في النافذة المنبثقة @@ -250,7 +250,7 @@ لا يمكن أن يكون اسم الملف فارغًا حدث خطأٌ ما: %1$s ملف مضغوط ZIP غير صالح - إزالة الفواصل المرجعية + إزالة الإشارة المرجعية تناسب مع الشاشة توليد تلقائي إستيراد @@ -600,8 +600,8 @@ زر الإجراء الثالث زر الإجراء الثاني زر الإجراء الأول - قياس الصورة المصغرة للفيديو المعروض في الإشعار من 16:9 إلى 1:1 نسبة العرض إلى الارتفاع (قد يؤدي إلى تشوهات) - مقياس الصورة المصغرة إلى نسبة عرض إلى ارتفاع 1:1 + قم بقص الصورة المصغرة للفيديو الموضحة في الإشعار من نسبة العرض إلى الارتفاع 16: 9 إلى 1: 1 + اقتصاص الصورة المصغرة إلى نسبة العرض إلى الارتفاع 1:1 امسح ملفات تعريف الارتباط التي يخزنها NewPipe عند حل reCAPTCHA تم مسح ملفات تعريف الارتباط reCAPTCHA امسح ملفات تعريف الارتباط reCAPTCHA @@ -684,7 +684,7 @@ جودة منخفضة (أصغر) جودة عالية (أكبر) معاينة مصغرة على شريط التمرير - علّمه كفيديو تمت مشاهدته + تعليم كفيديو تمت مشاهدته أُعجب بها منشئ المحتوى أظهر أشرطة ملونة لبيكاسو أعلى الصور تشير إلى مصدرها: الأحمر للشبكة والأزرق للقرص والأخضر للذاكرة إظهار مؤشرات الصور @@ -769,4 +769,5 @@ تنسيق غير معروف جودة غير معروفة حجم الفاصل الزمني لتحميل التشغيل + عرض مقاطع الفيديو المستقبلية \ No newline at end of file diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index 2d9257e3c..bd5948622 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -405,7 +405,7 @@ Sükut zamanı sürətlə irəlilə Yeni yayım bildirişləri Abunəliklərdən yeni yayımlar haqqında bildiriş göndər - Tezliyin yoxlanılması + Yoxlama tezliyi Tələb olunan şəbəkə bağlantısı İstənilən şəbəkə Tətbiq keçidində kiçildin @@ -496,7 +496,7 @@ Video oynadıcı Video fayl xülasəsi prosesi üçün bildirişlər Açın - Kiçik şəkili 1:1 aspekt nisbətinə ölçün + Miniatürü 1:1 aspekt nisbətinə kəsin Yükləmə intervalının həcmini dəyişdirin (hazırda %s). Daha aşağı dəyər ilkin video yükləməni sürətləndirə bilər. Dəyişikliklər oynadıcının yenidən başladılmasını tələb edir. Yayım yaradıcısı, məzmunu və ya axtarış sorğusu haqqında əlavə məlumat olan üst məlumat qutularını gizlətmək üçün söndürün Əlaqədar yayımı əlavə etməklə (təkrar etməyən) sonlanacaq oynatma sırasını davam etdir @@ -706,7 +706,7 @@ \nZəhmət olmasa ,Yaddaş Giriş Çərçivəsinə uyğun fayl menecerini quraşdırın. Bu video yalnız YouTube Music Premium üzvləri üçün əlçatandır, ona görə də NewPipe tərəfindən yayımlamaq və ya endirmək mümkün deyil. İndi açıqlamadakı mətni seçə bilərsiniz. Nəzərə alın ki, seçim rejimində səhifə titrəyə bilər və keçidlər kliklənməyə bilər. - Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 nisbətinə qədər ölçün (pozuntulara səbəb ola bilər) + Bildirişdə göstərilən video miniatürünü 16:9-dan 1:1 nisbətinə qədər kəsin Aşağıdakı bildiriş fəaliyyətini hər birinin üzərinə toxunaraq redaktə edin. Sağdakı təsdiq qutularından istifadə edərək yığcam bildirişdə göstərilməsi üçün onlardan üçə qədərini seçin Belə fayl/məzmun mənbəyi yoxdur Seçilmiş yayım xarici oynadıcılar tərəfindən dəstəklənmir @@ -717,4 +717,5 @@ Naməlum format Naməlum keyfiyyət Oynatma yükləmə intervalı həcmi + Gələcək videoları göstərin \ No newline at end of file diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index dc13a16c2..5ac2b1db8 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -446,7 +446,7 @@ Лиценз %s посочва следната причина: Маркери - Достъпност + Поверителност Език Вътрешен Включен @@ -553,4 +553,17 @@ Показвай цветни Picasso-панделки в горната част на изображенията като индикатор за техния произход (червен – от мрежата, син – от диска и червен – от паметта) Автоматична (тази на устройството) Мащабиране на миниатюрата в известието от 16:9 към 1:1 формат (възможни са изкривявания) + Покажи гледани + Избете плейлист + Известия + Изчистване на бисквитките от reCAPTCHA + Бисквитките от reCAPTCHA бяха почистени + Проверяване за актуализации… + , + Провери за актуализации + Процент + Неизвестно качество + Неизвестен формат + Наскоро добавено + Буфериране \ No newline at end of file diff --git a/app/src/main/res/values-bn-rBD/strings.xml b/app/src/main/res/values-bn-rBD/strings.xml index 02d35d384..1de4d3a50 100644 --- a/app/src/main/res/values-bn-rBD/strings.xml +++ b/app/src/main/res/values-bn-rBD/strings.xml @@ -459,4 +459,5 @@ দেখা হিসেবে মার্ক করো ট্যাগসমূহ অ্যান্ড্রয়েডকে থাম্বনেইলের প্রধান রং অনুযায়ী রঙিন করুন (উল্লেখ্য যে, এটি সব ডিভাইসে উপলব্ধ নয়) + অজানা ধরন \ No newline at end of file diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index a1acf9fd1..6a634a3a4 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -622,4 +622,6 @@ বাহ্যিক প্লেয়ারের জন্য কোনো অডিও স্ট্রিম নেই বাহ্যিক প্লেয়ারের জন্য কোনো ভিডিও স্ট্রিম নেই অজ্ঞাত মান + নতুন ধারার বিজ্ঞপ্তি + নেটওয়ার্ক সংযোগ দরকার \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 2e840629d..8ab1e2584 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -366,7 +366,7 @@ Vyčkávání Pozastaveno ve frontě - post-processing + zpracování Zařadit do fronty Akce odmítnuta systémem Stahování se nezdařilo @@ -384,7 +384,7 @@ Server neposílá data Server neakceptuje vícevláknové stahování, opakujte akci s @string/msg_threads = 1 Nenalezeno - Post-processing selhal + Zpracování selhalo Zastavit Maximální počet pokusů o opakování Maximální počet pokusů před zrušením stahování @@ -570,8 +570,8 @@ Třetí akční tlačítko Druhé akční tlačítko První akční tlačítko - Zmenšit miniaturu videa zobrazenou v oznámení z poměru stran 16: 9 na 1: 1 (může způsobit zkreslení) - Změnit poměr stran miniatury na 1:1 + Oříznout miniaturu videa zobrazenou v oznámení z poměru stran 16:9 na 1:1 + Oříznout poměr stran miniatury na 1:1 Ukázat memory leaks Zařazeno do fronty Zařadit do fronty @@ -584,10 +584,10 @@ Přibarvit oznámení Použít miniaturu pro pozadí zamknuté obrazovky a oznámení Zobrazit miniaturu - Oznámení o hašování videa + Oznámení o hashování Nedávné - Počítám haš - Oznámení o postupu hašování videa + Počítám hash + Oznámení o postupu hashování videa Vypnout, abyste skryli rámečky s meta informací s údaji o autorovi streamu, obsahu streamu nebo požadavků hledání Zobrazit meta informaci Kapitoly @@ -596,7 +596,7 @@ Zobrazit popis Otevřít s Na Vašem zařízení není aplikace, která to umí otevřít - Podobné strýmy + Podobné položky Vypnout pro skrytí popisu videa a doplňkové informace Zbořit aplikaci Stahování bylo zahájeno @@ -683,7 +683,7 @@ Vytvořit oznámení o chybě Kontrola aktualizací… Ukázat „Shodit přehrávač“ - Nové položky feedů + Nové položky Pro tuto akci nebyl nalezen žádný vhodný správce souborů. \nProsím, nainstalujte správce souborů kompatibilní se Storage Access Framework. Oznámení o hlášení chyb @@ -697,29 +697,38 @@ Změnit interval načítání (aktuálně %s). Menší hodnota může zrychlit počáteční načítání videa. Změna vyžaduje restart přehrávače. LeakCanary není dostupné Výchozí ExoPlayer - Nastavit oznámení k právě přehrávanému strýmu - Oznámení o nových strýmech - Oznámit o nových strýmech k objednání + Nastavit oznámení o právě přehrávaném streamu + Oznámení o nových streamech + Oznámit o nových strýmech od vašich odběrů Frekvence kontroly Jakákoli síť Nutné síťové připojení Smazat všechny stažené soubory z disku\? Objednali jste si nyní tento kanál Všechny přepnout - Nové strýmy - Oznámení o nových strýmech k objednání - Spustit kontrolu nových strýmů - Oznámení přehrávače + Nové streamy + Oznámení o nových streamech od odběrů + Spustit kontrolu nových streamů + Upozornění přehrávače Oznámení - Načítám podrobnosti o strýmu… + Načítám podrobnosti o streamu… Oznámení jsou vypnuta Přijímat oznámení , - %s nový strým - %s nové strýmy - %s nových strýmů + %s nový stream + %s nové streamy + %s nových streamů Procento Půltón + Velikost intervalu načtení přehrávání + Vybraný stream není podporován externími přehrávači + U externích přehrávačů nejsou dostupné žádné zvukové streamy + Neznámý formát + Neznámá kvalita + Zobrazit nadcházející videa + Streamy, které zatím nejsou podporovány systémem stahování, nebudou zobrazeny + Vyberte kvalitu pro externí přehrávače + U externích přehrávačů nejsou dostupné žádné video streamy \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4c8e9cfe1..5ff079f7a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,7 +1,7 @@ Veröffentlicht am %1$s - Kein Stream-Player gefunden. Möchtest du den VLC installieren\? + Kein Stream-Player gefunden. Möchtest du VLC installieren\? Installieren Abbrechen Im Browser öffnen @@ -161,7 +161,7 @@ Die meisten Sonderzeichen Wiedergabe fortsetzen Player - Nichts hier außer dem Zirpen der Grillen + Nichts hier, außer dem Zirpen der Grillen Möchtest du dieses Element aus dem Suchverlauf löschen\? Leere Seite Einen Kanal auswählen @@ -558,8 +558,8 @@ Dritte Aktionstaste Zweite Aktionstaste Erste Aktionstaste - Skaliert das in der Benachrichtigung angezeigte Vorschaubild von 16:9 auf ein 1:1 Seitenverhältnis (kann zu Verzerrungen führen) - Vorschaubild auf 1:1 Seitenverhältnis skalieren + Beschneidet das in der Benachrichtigung angezeigte Video-Vorschaubild von 16:9 auf ein 1:1 Seitenverhältnis + Vorschaubild auf 1:1 Seitenverhältnis zuschneiden Zufällig Puffern Wiederholen @@ -717,4 +717,5 @@ Streams, die noch nicht vom Downloader unterstützt werden, werden nicht angezeigt Der ausgewählte Stream wird von externen Playern nicht unterstützt Größe des Ladeintervalls für die Wiedergabe + Zukünftige Videos anzeigen \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index d9239547b..b1b11e24d 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -498,8 +498,8 @@ Κουμπί τρίτης ενέργειας Κουμπί δεύτερης ενέργειας Κουμπί πρώτης ενέργειας - Κλιμάκωση της μικρογραφίας βίντεο που εμφανίζεται στην ειδοποίηση από 16:9 σε αναλογία διαστάσεων 1:1 (μπορεί να προκαλέσει παραμορφώσεις) - Κλιμάκωση μικρογραφίας σε αναλογία διαστάσεων 1:1 + Περικοπή της μικρογραφίας βίντεο που εμφανίζεται στην ειδοποίηση από 16:9 σε αναλογία διαστάσεων 1:1 + Περικοπή μικρογραφίας σε αναλογία διαστάσεων 1:1 Φόρτωση Πιστεύετε ότι η ροή φορτώνει πολύ αργά; Δοκιμάστε να ενεργοποιήσετε τη γρήγορη φόρτωση (από τις ρυθμίσεις ή πατώντας το παρακάτω κουμπί). \n @@ -717,4 +717,5 @@ Άγνωστος τύπος αρχείου Άγνωστη ποιότητα Μέγεθος διαστήματος φόρτωσης αναπαραγωγής - + Εμφάνιση μελλοντικών βίντεο + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 56d529b04..edf4769e8 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -544,7 +544,7 @@ Copiar informe con formato Mostrando resultados para: %s Orden aleatorio - Escalar miniatura a relación de aspecto 1:1 + Recortar miniatura a relación de aspecto 1:1 Nunca Solo en Wi-Fi Comenzar reproducción automáticamente — %s @@ -558,13 +558,13 @@ Almacenar en memoria (búfer) Repetir ¡Puedes seleccionar como máximo tres acciones para mostrar en la notificación compacta! - Edita cada acción de notificación debajo pulsando sobre ella. Selecciona hasta tres de ellas para que aparezcan en la notificación compacta usando las casillas de verificación a la derecha + Edita cada una de las acciones en la notificación pulsando sobre ellas. Selecciona hasta tres de ellas para mostrarlas en la notificación compacta usando las casillas de verificación de la derecha. Botón de quinta acción Botón de cuarta acción Botón de tercera acción Botón de segunda acción Botón de primera acción - Escalar la relación de aspecto de la miniatura del vídeo mostrada en la notificación de 16:9 a 1:1 (puede ocasionar distorsiones) + Recortar la relación de aspecto de la miniatura del vídeo mostrada en la notificación de 16:9 a 1:1 Vaciar las cookies que NewPipe guarda al resolver un reCAPTCHA Mostrar contenido inapropiado para niños porque tiene un limite de edad (como 18+) Mostrar pérdidas de memoria @@ -574,7 +574,7 @@ Limpiar las cookies reCAPTCHA YouTube provee un «Modo restringido», el cual oculta contenido potencialmente solo apto para adultos Ajustar color de notificación - Permitir a Android personalizar el color de la notificación con el color principal de la imagen (ten en cuenta que esta opción no funciona en todos los dispositivos) + Permitir a Android personalizar el color de la notificación usando el color principal de la miniatura (ten en cuenta que esta opción no funciona en todos los dispositivos) Usar miniatura como fondo de pantalla de bloqueo y notificaciones Mostrar vista previa Desactivar para ocultar información adicional sobre el creador o contenido de la transmisión @@ -603,7 +603,7 @@ La descarga ha comenzado Resolver Puedes seleccionar tu tema nocturno favorito a continuación - Tema de Noche + Modo oscuro Selecciona tu tema nocturno favorito — %s Automático (tema del dispositivo) Mostrar detalles del canal @@ -719,4 +719,6 @@ Elija la calidad para reproductores externos Formato desconocido Calidad desconocida + Mostrar videos futuros + Tamaño del intervalo de carga de reproducción \ No newline at end of file diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 11b5266b0..2d776155c 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -426,8 +426,8 @@ Kolmas tegevusnupp Teine tegevusnupp Esimene tegevusnupp - Skaleeri teavituses kuvatav video pisipilt 16:9 külgede suhtest 1:1 suhtesse (võib põhjustada häireid) - Skaleeri pisipilt 1:1 küljesuhtesse + Kadreeri teavituses kuvatav video pisipilt 16:9 külgede suhtest 1:1 suhtesse + Kadreeri pisipilt 1:1 küljesuhtesse Arvutan räsi Hiljutised Kirjeldus @@ -716,4 +716,6 @@ Valitud meediavood ei ole toetatud välises pleieris Protsent Pooltoon + Taasesituseks vajalike andmete laadimise samm + Näita tulevasi videoid \ No newline at end of file diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 8098db146..c2d92c517 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -562,8 +562,8 @@ Hirugarren ekintzaren botoia Bigarren ekintzaren botoia Lehenego ekintzaren botoia - Eskalatu jakinarazpenetan erakusten den bideo miniaturaren formatu-ratioa 16:9tik 1:1era (distortsioak sor ditzake) - Miniatura 1:1 formatu-ratiora eskalatu + Ebaki jakinarazpenetan erakusten den bideo miniaturaren formatu-ratioa 16:9tik 1:1era + Miniatura 1:1 formatu-ratiora ebaki %s bilaketaren erantzunak erakusten Ilaran jarri da Jarri ilaran @@ -709,4 +709,13 @@ Beharrezko sare konexioa Portzentaia Semitonoa + Erreprodukzioaren kargatze-tartearen tamaina + Deskargatzaileak onartzen ez dituen jarioak ez dira erakusten + Hautatutako jarioa ez dago kanpoko erreproduzigailu batengatik onartuta + Ez dago kanpoko erreproduzigailu batengatik onartuta dagoen audio jariorik + Ez dago kanpoko erreproduzigailu batengatik onartuta dagoen bideo jariorik + Formatu ezezaguna + Kalitate ezezaguna + Erakutsi etorkizuneko bideoak + Hautatu kanpoko erreproduzigailuen kalitatea \ No newline at end of file diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 97fd534f1..6b607b6ae 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -557,8 +557,8 @@ سومین دکمه کنشی دومین دکمه کنشی اولین دکمه کنشی - تصویر بندانگشتی ویدیو که در اعلان نمایش می‌یابد، از نسبت ۱۶:۹ به ۱:۱ تغییر اندازه پیدا کند (ممکن است منجر به اعوجاج شود) - تغییر مقیاس تصویر بندانگشتی به نسبت ۱:۱ + تصویر بندانگشتی ویدیو که در اعلان نمایش می‌یابد، از نسبت ۱۶:۹ به ۱:۱ بریده می‌شود + برش تصویر بندانگشتی به نسبت ۱:۱ کیفیت پایین (کوچک‌تر) کیفیت بالا (بزرگ‌تر) نظرها از کار افتاده‌اند @@ -716,4 +716,6 @@ گزینش کیفیت برای پخش‌کننده‌های خارجی قالب ناشناخته کیفیت ناشناخته + اندازهٔ دورهٔ بار کردن پخش + نمایش ویدیوهای آینده \ No newline at end of file diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index d8c6682d6..758e522cd 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -12,7 +12,7 @@ I-download I-download ang stream file Maghanap - Ayos ng App + Pagsasaayos \"%1$s\" ba ang tinutukoy mo\? Ibahagi sa Gumamit ng ibang video player @@ -30,7 +30,7 @@ Pumili ng Tab Anong Bago Likuran - Popup + Naka-lutang Idagdag sa Download folder ng mga video Pumili ng download folder para sa mga video file @@ -257,4 +257,7 @@ Notipikasyon ng player Mga track Mga gumagamit + Hangganan ng Edad + Oo, pati na rin ang mga napanood nang video + Kusa (tema ng device) \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e09f292b0..4a79fbea9 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -6,7 +6,7 @@ Télécharger Dossier de téléchargement vidéo Choisissez le dossier de téléchargement des vidéos - Les fichiers vidéo téléchargés sont stockées ici + Les vidéos téléchargées sont stockées ici Installer Installer l’application Kore manquante \? Aucun lecteur de flux trouvé. Installer VLC \? @@ -99,7 +99,7 @@ Seuls certains appareils peuvent lire des vidéos 2K/4K Format vidéo par défaut Mémoriser les propriétés de la fenêtre flottante - Mémorise les dernières taille et position de la fenêtre flottante + Mémoriser les dernières taille et position de la fenêtre flottante Effacer G Le son peut être absent à certaines définitions @@ -551,7 +551,7 @@ Impossible de reconnaitre l’URL fournie. Voulez-vous l’ouvrir avec une autre application \? Ajouter automatiquement à la liste de lecture La liste de lecture du lecteur actif sera remplacée - Confirmer avant de supprimer la liste de lecture + Confirmer avant de supprimer une liste de lecture Rien Chargement Lire aléatoirement @@ -563,8 +563,8 @@ Troisième bouton d’action Deuxième bouton d’action Premier bouton d’action - Mettre à l\'échelle la miniature de la vidéo affichée dans la notification du format 16:9 au format 1:1 (peut provoquer des déformations) - Redimensionner la miniature au format 1:1 + Recadrer la miniature de la vidéo affichée dans la notification du format 16:9 au format 1:1 + Recadrer la miniature au format 1:1 Afficher les fuites de mémoire Ajouté à la file d’attente Ajouter à la file d’attente @@ -663,8 +663,8 @@ Balayez un élément pour le supprimer Ne pas lancer les vidéos dans le mini lecteur mais directement en plein écran si la rotation automatique est verrouillée. Vous pouvez toujours accéder au mini-lecteur en quittant le mode plein écran Lancer le lecteur principal en plein écran - Ajouter à la liste de lecture - Suivant dans la liste de lecture + Lire consécutivement + Video placée après la lecture Traitement en cours… Veuillez patienter Vérifier manuellement de nouvelles versions Vérification des mises à jour… @@ -719,4 +719,5 @@ Le flux séléctionné n\'est pas supporté par les lecteurs externes Aucun flux vidéo n\'est disponible pour les lecteurs externes Taille de l\'intervalle de chargement de la lecture + Afficher les futures vidéos \ No newline at end of file diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 5cf8abcf0..701d1b8cc 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -548,8 +548,8 @@ Terceiro botón de acción Cuarto botón de acción Quinto botón de acción - Escalar a miniatura do vídeo amosado na notificación da relación de aspecto 16:9 a 1:1 (pode intruducir distorsións) - Escala miniatura á relación de aspecto 1:1 + Cortar a miniatura do vídeo amosado na notificación da relación de aspecto 16:9 a 1:1 + Cortar miniatura á relación de aspecto 1:1 Apagado Modo tableta Abrir sitio Web @@ -709,4 +709,13 @@ As notificacións están desactivadas Recibir notificacións Agora está subscrito a esta canle + Emisións non soportadas polo descarregador non son mostradas + Seleccione a calidade para reprodutores externos + Formato descoñecido + Calidade descoñecida + Tamaño do intervalo de carregamento da reprodución + As emisións seleccionadas non son soportadas polos reprodutores externos + Non hai emisións de vídeo dispoñíbeis para reprodutores externos + Ver futuros vídeos + Non hai emisións de audio dispoñíbeis para reprodutores externos \ 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 de5128493..b9056922a 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -580,8 +580,8 @@ כפתור פעולה שלישי כפתור פעולה ראשון כפתור פעולה שני - לשנות את יחס התצוגה הממוזערת שמופיעה בהתראות מיחס תצוגה של 16:9 ל־1:1 (עשוי לעוות את התמונה) - שינוי גודל התצוגה הממוזערת ליחס תצוגה 1:1 + חיתוך התצוגה הממוזערת שמופיעה בהתראות מיחס תצוגה של 16:9 ל־1:1 + חיתוך התצוגה הממוזערת ליחס תצוגה 1:1 הצגת דליפות זיכרון נוסף לתור הוספה לתור @@ -742,4 +742,6 @@ אין תזרימי וידאו שזמינים לנגנים חיצוניים בחירת איכות לנגנים חיצוניים תצורה לא מוכרת + גודל משך טעינת נגינה + הצגת סרטונים עתידיים \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index cb7db9f44..439835e06 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -1,63 +1,63 @@ - Za početak dodirnite povećalo. + Počni dodirom na povećalo. Objavljeno %1$s - Reproduktor za stream nije pronađen. Instalirati VLC\? + Nije pronađen nijedan player streamova. Želiš li instalirati VLC\? Instaliraj Odustani Otvori u pregledniku Otvori skočni prozor - Podijeli + Dijeli Preuzimanje - Pretraživanje + Pretraga Postavke - Jeste li mislili „%1$s”\? - Podijeli pomoću - Koristi vanjski reproduktor videozapisa - Uklanja zvuk pri nekim rezolucijama - Koristi vanjski reproduktor za zvuk + Misliš li „%1$s”\? + Dijeli s + Koristi vanjski video player + Uklanja audiosnimku pri nekim rezolucijama + Koristi vanjski audio player Pretplati se Pretplaćeno - Pretplata na kanalu otkazana - Nije moguće promijeniti pretplatu - Nije moguće osvježiti pretplatu + Pretplata na kanal otkazana + Nije bilo moguće promijeniti pretplatu + Nije bilo moguće aktualizirati pretplatu Pretplate Što je novo Pozadina Skočni prozor Mapa za preuzimanje videozapisa - Preuzeti videozapisi spremaju se ovdje - Odaberi mapu za preuzimanje videozapisa - Mapa za preuzimanje zvuka - Preuzete audio-datoteke spremaju se ovdje - Odaberi mapu za preuzimanje audio-datoteka - Standardna rezolucija - Standardna rezolucija skočnog prozora - Prikaži više rezolucije - Samo neki uređaji podržavaju reprodukciju 2K/4K videozapisa + Preuzete video datoteke se spremaju ovdje + Odaberi mapu za preuzimanje video datoteka + Mapa za preuzimanje audiosnimaka + Preuzete datoteke audiosnimaka se spremaju ovdje + Odaberi mapu za preuzimanje datoteka audiosnimaka + Zadana rezolucija + Zadana rezolucija skočnog prozora + Prikaži veće rezolucije + Samo neki uređaji podržavaju reprodukciju 2K/4K videa Reproduciraj s Kodijem Instalirati nedostajući Kore program\? - Prikaži opciju \"Reproduciraj putem Kodija\" + Prikaži opciju „Reproduciraj pomoću Kodija” Prikaži opciju za reproduciranje videozapisa putem Kodija - Zvuk - Zadani format zvuka - Zadani format videozapisa + Audiosnimka + Zadani audio format + Zadani video format Tema Svijetla Tamna Crna Zapamti veličinu i poziciju skočnog prozora Zapamti posljednju veličinu i poziciju skočnog prozora - Prijedlozi pri traženju + Prijedlozi pretrage Odaberi prijedloge koji se prikazuju pri traženju Povijest pretraživanja - Svaku pretragu spremi lokalno - Pregledna Povijest - Spremaj povijest gledanja + Spremi pretrage lokalno + Povijest gledanja + Prati gledana videa Nastavi reprodukciju Nastavi reproducirati nakon prekidanja (npr. telefonski pozivi) Preuzmi - Prikaži \'Sljedeće\' i \'Slične\' videozapise + Prikaži videa „Sljedeći” i „Slični” URL nije podržan Zadani jezik sadržaja Video i audio @@ -76,11 +76,11 @@ Najbolja rezolucija Greška Greška u mreži - Nije moguće učitati sve ikone - Nije moguće dešifrirati URL potpis videozapisa - Nije moguće dohvatiti stranicu + Nije bilo moguće učitati sve sličice + Nije bilo moguće dešifrirati URL potpis videozapisa + Nije bilo moguće obraditi stranicu Sadržaj nije dostupan - Nije moguće postaviti izbornik za preuzimanje + Nije bilo moguće postaviti izbornik za preuzimanje Program/korisničko sučelje su preknuli raditi Oprosti, ovo se nije trebalo dogoditi. Prijavi pogrešku putem e-maila @@ -89,14 +89,14 @@ Informacije: Što se dogodilo: Što:\\nZahtjev:\\nJezik sadržaja:\\nZemlja sadržaja:\\nJezik programa:\\nUsluga:\\nGMT vrijeme:\\nPaket:\\nVerzija:\\nVerzija OS-a: - Vaš komentar (na engleskom): + Tvoj komentar (na engleskom): Detalji: Pokreni video, trajanje: - Profilna slika prenositelja - Goreglasovi - Doljeglasovi - Videozapis - Zvuk + Sličica avatara prenositelja + Sviđanja + Nesviđanja + Video + Audio Pokušaj ponovo tis. mil @@ -104,26 +104,26 @@ Počni Pauziraj Izbriši - Kontrolna suma + Kontrolni zbroj U redu Naziv datoteke - Niti + Komponente procesa Greška - NewPipe preuzima - Dodirni za detalje - Molimo pričekajte… + NewPipe preuzimanje + Dodirni za prikaz detalja + Pričekaj … Kopirano u međuspremnik - Molimo kasnije u postavkama odaberite mapu za preuzimanje + Odaberi mapu za preuzimanje kasnije u postavkama Ova dozvola je potrebna za \notvaranje skočnog prozora reCAPTCHA zadatak - Traži se reCAPTCHA zadatak + Zatražen je reCAPTCHA zadatak Preuzimanja Dozvoljeni znakovi u nazivima datoteka Nedozvoljeni znakovi su zamjenjeni ovima Znak za zamjenu Slova i brojevi - Posebni znakovi + Najviše posebnih znakova O NewPipeu Licence treće strane © %1$s od %2$s pod %3$s @@ -131,15 +131,15 @@ Licence Slobodan i mali YouTube program za Android. Pogledaj na GitHubu - Licenca za NewPipe + NewPipe licenca Ako imate ideja za prijevod, promjene u dizajnu, čišćenje koda ili neke veće promjene u kodu, pomoć je uvijek dobro došla. Što više radimo, to bolji postajemo! Pročitaj licencu Doprinos Povijest Povijest NewPipe obavijest - Obavijesti za NewPipe reproduktore - Reproduktor + Obavijesti za NewPipe playera + Player Ponašanje Povijest i predmemorija Poništi @@ -155,70 +155,70 @@ %s pregled %s pregleda - %s pregledi + %s pregleda - Nema videozapisa + Nema videa - %s videozapis - %s videozapisa - %s videozapisa + %s video + %s videa + %s videa Reproduciraj sve - Nije moguće reproducirati ovaj stream - Dogodila se neoporavljiva greška reproduktora - Oporavljanje od greške reproduktora + Nije bilo moguće reproducirati ovaj stream + Dogodila se neoporavljiva greška playera + Oporavljanje od greške playera Prikaži savjet za držanje - Prikaži savjet kad se pritisne gumb za pozadinsku ili skočni prozor u detaljima videa: - Želite li izbrisati ovu stavku iz povijesti pretraživanja? + Prikaži savjet kad se pritisne gumb za pozadinu ili skočni gumb u videu „Detalji:” + Želiš li izbrisati ovu stavku iz povijesti pretrage\? Sadržaj Prazna stranica - Kiosk stranica - Kanal - Odaberite kanal - Niste pretplaćeni na nijedan kanal - Odaberite kiosk + Stranica kioska + Stranica kanala + Odaberi kanal + Još nema pretplata na nijedan kanal + Odaberi jedan kiosk U trendu - Vrh 50 - Novo i popularno + 50 najboljih + Novi i popularni Ukloni Detalji - Postavke zvuka + Postavke za audiosnimke Drži pritisnuto za dodavanje u popis izvođenja [Nepoznato] Doniraj - Web stranica + Web-stranica Započni reprodukciju u pozadini Reproduciraj u skočnom prozoru Otvori ladicu Zatvori ladicu - Video reproduktor - Pozadinski reproduktor - Skočni reproduktor + Video player + Pozadinski player + Skočni player Uvjek pitaj Dohvaćanje podataka … - Učitava se odabrani sadržaj + Učitavanje traženog sadržaja Nova playlista Preimenuj Ime Dodaj u playlistu - Postavi kao minijaturu playliste + Postavi kao sličicu playliste Zabilježi playlistu Ukloni zabilješku Izbrisati ovu playlistu\? Playlista je stvorena - Dodano kao playlistu - Minijatura playliste se promijenila. + Dodano u playlistu + Sličica playliste je promijenjena. Bez titlova - Popuni - Ispuniti - Povećaj - Auto generirano + Prilagodi + Ispuni + Zumiraj + Automatski generirani Praćenje curenja memorije može uzrokovati greške u radu programa prilikom odlaganje gomile Izvijesti o krajevima životnog ciklusa Prikaži informacije - Zabilježeni popisi + Zabilježene playliste Dodaj u - Učitaj slike + Učitaj sličice Slikovna predmemorija obrisana Izbriši metapodatke iz predmemorije Kanali @@ -233,7 +233,7 @@ Prijeđi na glavni Uvezi bazu podataka Izvezi bazu podataka - Poništava vašu trenutačnu povijest, pretplate, playliste i (opcionalno) postavke + Poništava tvoju trenutačnu povijest, pretplate, playliste i (opcionalno) postavke Izvezi povijest, pretplate, playliste i postavke Izbriši povijest gledanja Briše povijest reproduciranih streamova i pozicije reprodukcije @@ -245,29 +245,29 @@ Nema takve mape Naziv datoteke ne može biti prazan Dogodila se greška: %1$s - Povucite za promjenu redoslijeda + Povuci za promjenu redoslijeda Stvori Odbaci Preimenuj 1 stavka izbrisana. Nijedan program nije instaliran za reprodukciju ove datoteke Vrati - Posjetite web stranicu NewPipe za više informacija i vijesti. - NewPipeova pravila o privatnosti - Pročitajte pravila o privatnosti + Posjeti NewPipe web-stranicu za više informacija i vijesti. + NewPipe pravila o privatnosti + Pročitaj pravila o privatnosti Zadnje svirano - Najviše svirano + Najviše reproducirano Izvezeno Uvezeno Nema važeće ZIP datoteke Upozorenje: Nije moguće uvesti sve datoteke. - Ovo će poništiti vaše trenutne postavke. - Želite li također uvesti postavke? + Ovo će prepisati tvoje trenutačne postavke. + Želiš li također uvesti postavke\? Uvoz Uvoz iz Izvoz u - Uvoz… - Izvoz… + Uvoz … + Izvoz … Uvoz datoteke Prethodni izvoz Nije bilo moguće uvesti pretplate @@ -277,14 +277,14 @@ \n1. Idi na ovaj URL: %1$s \n2. Prijavi se \n3. Pritisni „Uključeni svi podaci”, zatim „Poništi odabir svih”, a zatim odaberi samo „pretplate” i pritisni „U redu” -\n4. Pritisni na „Nastavi”, a zatim „Stvori izvoz” -\n5. Pritisni na „Preuzmi” -\n6. Dolje pritisni na UVEZI DATOEKU i odaberi .zip datoteku za peuzimanje -\n7. [Ako uvoz .zip datoteke ne uspije] Izdvoji .csv datoteku (pod \"YouTube and YouTube Music/subscriptions/subscriptions.json\"). Dolje pritisni UVEZI DATOTEKU i odaberi izdvojenu csv datoteku - vašID, soundcloud.com/vašID - Uzmite u obzir da ova operacija može uzrokovat veliku potrošnju prometa. +\n4. Pritisni „Sljedeći korak”, a zatim „Stvori izvoz” +\n5. Pritisni gumb „Preuzmi” nakon što se pojavi +\n6. Dolje pritisni UVEZI DATOEKU i odaberi preuzetu .zip datoteku +\n7. [Ako uvoz .zip datoteke ne uspije] izdvoji .csv datoteku (pod „YouTube and YouTube Music/subscriptions/subscriptions.json”), dolje pritisni UVEZI DATOTEKU i odaberi izdvojenu csv datoteku + tvojID, soundcloud.com/tvojid + Ova operacija može prouzročiti veliku potrošnju mrežnog prometa. \n -\nŽelite li nastaviti? +\nŽeliš li nastaviti\? Kontrole brzine reprodukcije Premotaj naprijed tijekom šutnje Korak @@ -294,19 +294,19 @@ Bez ograničenja Ograniči rezoluciju tijekom korištenja mobilnih podataka Nijedan - Reproduktor za stream nije pronađen (možeš instalirati VLC za reprodukciju). + Nije pronađen nijedan player streamova (možeš instalirati VLC za reprodukciju). Preuzmi datoteku streama Koristi brzo netočno premotavanje - Netočno premotavanje omogućava reproduktoru da premota brže uz manju točnost. Premotavanje od 5, 15 ili 25 sekundi s ovime nije moguće + Netočno premotavanje omogućuje playeru brže premotavanje uz manju točnost. Premotavanje od 5, 15 ili 25 sekundi s ovime ne radi Otkaži pretplatu Odaberi karticu - Ažuriranja + Aktualiziranja Događaji Datoteka obrisana Obavijest za nove NewPipe verzije Briše povijest ključnih riječi pretraživanja Vanjska pohrana nije dostupna - Ažuriranja + Aktualiziranja Prikaži obavijest i zatraži aktualiziranje programa kad je dostupna nova verzija Popis Popločeno @@ -314,51 +314,51 @@ Dodirni za preuzimanje Preuzimanje nije uspjelo Prikaži pogrešku - Isključi za sprječavanje učitavanja sličica, čime se štede podatci i memorija. Promjena postavke čisti predmemoriju u radnoj memoriji i u pohrani + Isključi za sprečavanje učitavanja sličica, čime se štedi korištenje podataka i memorije. Promjene čiste predmemoriju slika radne memorije i diska Izbriši sve podatke web-stranica iz predmemorije Metapodaci su izbrisani Automatski dodaj sljedeći stream u popisa izvođenja - Nastavi završavati (ne ponavljajući) popis izvođenja dodavanjem povezanog streama + Nastavi završavati (ne ponavljajući) popis reprodukcija dodavanjem povezanog streama Kontrola glasnoće pomoću gesti - Koristi geste za kontrolu glasnoće + Koristi geste za upravljanje glasnoćom playera Kontrola svjetline pomoću gesti - Koristi gesture za kontrolu svjetline + Koristi gesture za upravljanje svjetlinom playera Zadana zemlja sadržaja Otkrivanje grešaka Obavijest o novoj verziji programa Preuzimanje na vanjsku SD karticu nije moguće. Ponovo postaviti lokaciju mape za preuzimanje\? - Vanjski reproduktori ne podržavaju ove vrste veza + Vanjski playeri ne podržavaju ove vrste poveznica Nije pronađen nijedan videozapis - Nije pronađen nijedan audio zapis + Nije pronađena nijedna audiosnimka Nema takve datoteke/izvora sadržaja Datoteka ne postoji ili joj nedostaje dopuštenje za čitanje ili pisanje Nema dostupnih zapisa za preuzimanje - Neuspjelo čitanje spremljenih kartica, stoga se koriste zadane - Vratiti zadane - Želite li vratiti zadane postavke\? + Nije bilo moguće čitati spremljene kartice, stoga se koriste zadane + Obnovi standardne vrijednosti + Želiš li obnoviti standardne vrijednosti\? Broj pretplatnika nije dostupan - NewPipe razvijaju volonteri koji provode vrijeme donoseći vam najbolje iskustvo. Vratite im kako biste programerima učinili da NewPipe bude još bolji dok uživate u šalici kave. + NewPipe razvijaju volonteri koji provode vrijeme kako bi doprinijeli najboljem iskustvu. Doprinesi programerima kako bi poboljšali NewPipe dok uživaju u šalici kave. Koje su kartice prikazane na glavnoj stranici Konferencije - Željena radnja otvaranja streama + Željena radnja za otvaranje Zadana radnja pri otvaranju sadržaja — %s Titlovi - Promijeni veličinu podnaslova reproduktora i pozadinske stilove reproduktora. Za stupanje na snagu, program se mora ponovo pokrenuti - Prisilno izvješćivanje o greškama Rx-a koje se ne mogu isporučiti izvan \'fragmenta\' ili životnog ciklusa aktivnosti nakon odlaganja - Uvezite SoundCloud profil tako da upišete URL ili svoj ID: + Promijeni veličinu podnaslova i pozadinske stilove playera. Zahtijeva ponovno pokretanje programa + Prisilno izvijesti o neisporučivim Rx iznimaka izvan fragmenta ili životnog ciklusa aktivnosti nakon odlaganja + Uvezi SoundCloud profil upisom URL-a ili svog ID-a: \n -\n1. Omogućite \"način rada na radnoj površini\" u web-pregledniku (stranica nije dostupna na mobilnim uređajima) -\n2. Idite na ovaj URL: %1$s -\n3. Ulogirajte se -\n4. Kopirajte URL profila na koji ste preusmjereni. - brzina +\n1. Omogući „način rada na radnoj površini” u web-pregledniku (stranica nije dostupna na mobilnim uređajima) +\n2. Idi na ovaj URL: %1$s +\n3. Prijavi se +\n4. Kopiraj URL profila na koji te se preusmjerava. + Brzina Visina tona Odspoji (može prouzročiti izobličenje) - Sklopi prilikom mijenjanje programa - Radnja prilikom prebacivanja na drugi program iz glavnog video reproduktora – %s - Smanji na pozadinski reproduktor - Smanji na skočni reproduktor - Način prikaza popisa + Smanji prilikom mijenjanje programa + Radnja prilikom prebacivanja na drugi program iz glavnog video playera – %s + Smanji na pozadinski player + Smanji na skočni player + Način prikaza kao popis Automatski Gotovo Na čekanju @@ -367,18 +367,18 @@ naknadna obrada Popis izvođenja Sustav je odbio radnju - Generirajte jedinstveni naziv + Generiraj jedinstveni naziv Prepiši Datoteka s tim nazivom već postoji Preuzeta datoteka s tim nazivom već postoji Datoteka s ovim nazivom se već preuzima Odredišna mapa ne može biti stvorena Datoteka se ne može stvoriti - Nije moguće uspostaviti sigurnu vezu - Nije moguće pronaći server + Nije bilo moguće uspostaviti sigurnu vezu + Nije bilo moguće pronaći server Nije moguće povezati se s serverom Server ne šalje podatke - Poslužitelj ne prihvaća preuzimanja s više niti, pokušaj ponovo s @string/msg_threads = 1 + Poslužitelj ne prihvaća preuzimanja višestrukih procesa, pokušaj ponovo s @string/msg_threads = 1 Nije pronađeno Naknadna obrada nije uspjela Stop @@ -390,23 +390,23 @@ Isključi, kako bi se komentari sakrili Automatska reprodukcija Nema komentara - Komentare nije moguće učitati + Nije bilo moguće učitati komentare Zatvori - NewPipe je copyleft libre software: Može se koristiti, proučavati i poboljšavati po volji. Konkretno, može se redistribuirati i / ili modificirati pod uvjetima GNU opće javne licence koju je objavila Free Software Foundation, bilo licence verzije 3, ili (po vlastitom izboru) bilo koje kasnije verzije. + NewPipe je copyleft libre softver: Može se koristiti, proučavati i poboljšavati po volji. Konkretno, može se redistribuirati i / ili modificirati pod uvjetima GNU opće javne licence koju je objavila zaklada Free Software Foundation, pod verzijom 3 licence, ili (po vlastitom izboru) bilo koje kasnije verzije. Projekt NewPipe ozbiljno shvaća tvou privatnost. Stoga program ne prikuplja nikakve podatke bez tvog pristanka. \nNewPipe pravila o privatnosti detaljno objašnjavaju koji se podaci šalju i spremaju kad šalješ izvještaje o prekidu rada programa. - Kako bismo se uskladili s Europskom općom uredbom o zaštiti podataka (GDPR), upozoravamo vas na politiku privatnosti tvrtke NewPipe. Pažljivo ga pročitajte. -\nZa slanje izvješća o pogreškama potrebno je prihvatiti politiku privatnosti. + Kako bismo se uskladili s Europskom općom uredbom o zaštiti podataka (GDPR), ovime upozoravamo na NewPipe politiku privatnosti. Pažljivo je pročitaj. +\nZa slanje izvješća o pogreškama moraš prihvatiti politiku privatnosti. Nastavi reprodukciju - Vrati zadnji položaj reprodukcije + Obnovi zadnji položaj reprodukcije Pozicije na popisima - Prikaži poziciju reprodukcije na listi + Prikaži poziciju reprodukcije u popisima Obriši podatke Pozicije reprodukcije su izbrisane Datoteka je premještena ili izbrisana Datoteka s ovim nazivom već čeka na preuzimanje Vrijeme povezanosti je isteklo - Želite li očistiti povijest preuzimanja ili izbrisati sve preuzete datoteke\? + Želiš li izbrisati povijest preuzimanja ili izbrisati sve preuzete datoteke\? Započni preuzimanja Zaustavi preuzimanja Pitaj gdje preuzeti @@ -416,50 +416,50 @@ Nitko ne gleda Nitko ne sluša Jezik će se promijeniti nakon ponovnog pokretanja programa - Zadani Kiosk + Standardni kiosk Podržani su samo HTTP URL-ovi - Lokalno - Nedavno dodano - Autogenerirano (prenositelj nedefiniran) + Lokalni + Nedavno dodani + Automatski generirano (prenositelj nedefiniran) Očisti povijest preuzimanja Izbriši preuzete datoteke Dopusti prikaz iznad drugih programa Jezik programa Zadani sustav - Videozapisi - Isključi + Videa + Isključi zvuk Uključi Pomoć - Učitavanje feeda… - Želite li izbrisati ovu grupu\? - Novi - Uvijek ažuriraj + Učitavanje feeda … + Želiš li izbrisati ovu grupu\? + Nova + Uvijek aktualiziraj Omogući brz način Onemogući brz način Memorija uređaja je popunjena - Najomiljenije - Pritisnite \"Gotovo\" kad riješeno + Najomiljeniji + Pritisni „Gotovo” kad je riješeno Gotovo - ∞ videozapisa - 100+ videozapisa - Prijavite grešku na GitHub + ∞ videa + Više od 100 videa + Prijavi grešku na GitHub-u Umjetnici Albumi Pjesme - Napravio %s + Stvoren od %s Nikada Ograniči popis preuzimanja Koristi birač mapa sustava (SAF) Ukloni pregledano - Ukloni pogledane videozapise\? + Ukloni pogledana videa\? %d sekunda - %d sekundi + %d sekunde %d sekundi - %d minutu - %d minuta + %d minuta + %d minute %d minuta @@ -469,7 +469,7 @@ %d sat - %d sati + %d sata %d sati Nije učitano: %d @@ -481,63 +481,63 @@ Gumb druge radnje Gumb prve radnje Prikazuju se rezultati za: %s - Nije moguće prepoznati URL. Želiš li otvoriti s drugim programom\? - Smanjiti omjer minijatura na 1:1 + Nije bilo moguće prepoznati URL. Želiš li otvoriti s drugim programom\? + Odreži sličicu na omjer 1:1 Učitavanje u predmemoriju Istovremeno se pokreće jedno preuzimanje Dodano u popis izvođenja Dodaj u popis izvođenja - Popis izvođenja + Reproduciraj popis izvođenja Automatski popis izvođenja - Popis izvođenja aktivnog reproduktora će se zamijeniti - Prebacivanje s jednog reproduktora na drugi može zamijeniti popisa izvođenja + Popis izvođenja aktivnog playera će se zamijeniti + Prebacivanje s jednog playera na drugi može zamijeniti tvoj popis izvođenja Pitaj prije pražnjenja popisa izvođenja %s slušatelj %s slušatelja %s slušatelja - datoteka ne može biti prepisana - Promijeni omjer prikazane minijature videa u obavijesti iz 16:9 na 1:1 (može prouzročiti izobličenja) + datoteka se ne može prepisato + Odreži prikazane sličice videa u obavijesti iz omjera 16:9 na 1:1 U kompaktnom prikazu obavijesti mogu se odabrati najviše 3 radnje! Od %s - Minijatura avatara kanala + Sličica avatara kanala Dohvati iz određenog feeda kad je dostupno Vrijeme nakon zadnjeg aktualiziranja prije nego što se pretplata smatra zastarjelom – %s - Prag aktualiziranja feedova + Prag za aktualiziranje feedova Feed Prikaži samo negrupirane pretplate Prazno ime grupe - %d odabrani - %d odabrana + %d odabrana + %d odabrane %d odabranih - Obrada feeda … + Obrada feeda u tijeku … Zadnje aktualiziranje feeda: %s Grupe kanala - Da, i djelomično pogledane videozapise - Odaberi primjerak + Da, i djelomično pogledana videa + Odaberi jednu instancu Aplikacija će te pitati kamo spremati preuzimanja. \nOmogući birač mapa sustava (SAF), ako želiš preuzimati na vanjsku SD karticu Nije moguće obnoviti ovo preuzimanje Napredak je izgubljen, jer je datoteka izbrisana NewPipe se zatvorio tijekom rada s datotekom Stranica playliste - Videzapisi koji su gledani prije i nakon dodavanja u playlistu će se ukloniti. + Videa koji su gledani prije i nakon dodavanja u playlistu će se ukloniti. \nStvarno ih želiš ukloniti\? Ovo je nepovratna radnja! Još nema zabilježenih playlista Odaberi playlistu obnavljanje Samo na Wi-Fi mreži - Pokreni automatski – %s + Pokreni reprodukciju automatski – %s Prikaži curenje memorije %s gledatelj %s gledatelja %s gledatelja - Uklj/Isklj uslugu, trenutačno odabrana: + Uključi/isključi uslugu, trenutačno odabrana: Kopiraj formatirani izveštaj Izbriši kolačiće koje NewPipe sprema nakon rješavanja reCAPTCHA reCAPTCHA kolačići su izbrisani @@ -548,51 +548,51 @@ YouTube nudi postavku „Ograničeni način rada”, čime se skriva sadržaj za odrasle Uključi YouTube postavku „Ograničeni način rada” Prikaži sadržaj koji vjerojatno nije prikladan za djecu, jer je dobno ograničen (kategorija 18) - Primjerak već postoji - Neuspjela provjera primjerka - Upiši URL primjerka - Dodaj primjerak - Pronađi omiljene primjerke na %s - Odaberi tvoje omiljene PeerTube primjerke - PeerTube primjerci + Instanca već postoji + Nije bilo moguće provjeriti instancu + Upiši URL instance + Dodaj instancu + Pronađi instance koje voliš na %s + Odaberi svoje omiljene PeerTube instance + PeerTube instance Vrijeme premotavanja prema naprijed ili natrag Ništa Promiješaj Ponovi - Provjeri je li tvoj problem već postoji. Dupla pojava problema krade nam vrijeme koje bismo mogli utrošiti na ispravljanje same greške. + Provjeri je li problem već postoji. Prijavljivanje istog već prijavljenog problema krade nam vrijeme koje bismo mogli utrošiti na ispravljanje greške. Za uređivanje radnji u obavijestima, dodirni ih. Označi do tri radnje za prikaz u kompaktnoj obavijesti koristeći oznake na desnoj strani - Zbog ograničenja ExoPlayera, trajanje traženja postavljeno je na %d s - Neka Android prilagodi boju obavijesti prema glavnoj boji minijature (ovo nije dostupno na svim uređajima) + Zbog ograničenja ExoPlayera, trajanje premotavanja postavljeno je na %d s + Neka Android prilagodi boju obavijesti prema glavnoj boji sličice (ovo nije dostupno na svim uređajima) Oboji obavijest NewPipe još ne podržava ovaj sadržaj. \n \nNadamo se da će biti podržan u budućoj verziji. - Prikaži minijaturu kao pozadinu pri zaključanom ekranu i unutar obavijesti - Prikaži minijaturu + Koristi sličicu za pozadinu zaključanog ekrana i za obavijesti + Prikaži sličicu Prikaži izvorno vrijeme elemenata - „Okvir za pristup spremištu” omogućuje preuzimanje na SD karticu + „Storage Access Framework” omogućuje preuzimanje na SD karticu Izvorni tekstovi usluga bit će vidljivi u elementima prijenosa Dostupno je u nekim uslugama. Obično je puno brže, ali može dohvatiti ograničenu količinu stavki i često nepotpune podatke (npr. bez trajanja, vrste stavke, bez stanja uživo) - Mislite li da je učitavanje feeda prespor\? Ako je to slučaj, pokušajte omogućiti brzo učitavanje (možete ga promijeniti u postavkama ili pritiskom na donji gumb). + Misliš da je učitavanje feeda presporo\? Ako da, pokušaj omogućiti brzo učitavanje (možeš ga promijeniti u postavkama ili pritiskom na donji gumb). \n -\nNewPipe nudi dvije strategije ulaganja feeda: -\n• Dohvaćanje cijelog pretplatničkog kanala, koji je spor, ali cjelovit. -\n• Korištenje namjenske krajnje točke usluge, koja je brza, ali obično nije potpuna. +\nNewPipe nudi dvije strategije učitavanja feeda: +\n• Dohvaćanje cijelog pretplatničkog kanala, što je sporo, ali cjelovito. +\n• Korištenje namjenske krajnje točke usluge, što je brzo, ali obično nepotpuno. \n -\nRazlika je u tome što brzom obično nedostaju neke informacije, poput trajanja ili vrste stavke (ne može razlikovati videozapise uživo od uobičajenih), a možda će vratiti i manje predmeta. +\nRazlika je u tome što brzom načinu obično nedostaju neke informacije, poput trajanja ili vrste predmeta (ne može razlikovati videa uživo od običnih videa), a možda će vratiti i manje predmeta. \n \nYouTube je primjer usluge koja nudi ovaj brzi način sa svojim RSS feedom. \n -\nDakle, izbor se svodi na ono što više volite: brzinu ili precizne informacije. +\nDakle, izbor se svodi na ono što više voliš: brzinu ili precizne informacije. Izračunavanje šifriranja Obavijest šifriranja videa Obavijesti o napretku šifriranja videa Nedavni Isključi za skrivanje polja metapodataka s dodatnim podacima o autoru streama, sadržaju streama ili zahtjevu za pretraživanje Prikaži metapodatke - Slični videozapisi + Povezani predmeti Nijedan program na tvom uređaju ovo ne može otvoriti - Poglavlja videozapisa + Poglavlja Opis Komentari Isključi za skrivanje opisa videozapisa i dodatnih informacija @@ -620,12 +620,12 @@ Isklj. Uklj. Način rada na tabletu - Otvori stranicu - Srce autora + Otvori web-stranicu + Od autora obilježeno srcem Privatno Nenavedeno Javno - URL minijature + URL sličice Poslužitelj Podrška Jezik @@ -633,41 +633,41 @@ Licenca Oznake Kategorija - Onemogućite odabir teksta u opisu - Omogućite odabir teksta u opisu + Onemogući biranje teksta u opisu + Omogući biranje teksta u opisu Račun ukinut Prikaži pogledane stavke Autorov račun je ukinut. -\nNewPipe ubuduće neće moći učitavati ovaj feed. -\nŽelite li otkazati pretplatu na ovaj kanal\? - Nije moguće učitati feed za \'%s\'. +\nNewPipe ubuduće neće moći učitavati ovaj feed. +\nŽeliš li otkazati pretplatu na ovaj kanal\? + Nije bilo moguće učitati feed za „%s”. Pogreška pri učitavanju feeda - Počevši od Androida 10, podržan je samo \'Storage Access Framework\' + Počevši od Androida 10, podržano je samo radno okruženje „Storage Access Framework” Od vas će se tražiti gdje spremiti svako preuzimanje Ne prikazuj - Niska kvaliteta (manji) + Niska kvaliteta (manja) Visoka kvaliteta (veća) - Pregled sličica trake za pretraživanje - Mapa za preuzimanje još nije postavljena, odaberite zadanu mapu za preuzimanje + Pregled sličica premotavanja + Mapa za preuzimanje još nije postavljena, odaberi standardnu mapu za preuzimanje Komentari su onemogućeni Označi kao pogledano Način rada brzog 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. - %s daje ovaj razlog: - Obrada... Pričekajte trenutak - Povucite stavke da biste ih uklonili + %s pruža ovaj razlog: + Obrada u tijeku … Može malo potrajati + Za ukljanjanje stavki povuci ih Prikazati indikatore slike - Dovršeno %s preuzimanje - Dovršena %s preuzimanja - Dovršeno %s preuzimanja + Preuzimanje je gotovo + %s preuzimanja su gotova + %s preuzimanja su gotova - Pokreni glavni reproduktor u cjeloekranskom prikazu - Reproduciraj sljedeće - U redu čekanja - Prikažite Picassove vrpce u boji na slikama koje označavaju njihov izvor: crvena za mrežu, plava za disk i zelena za memoriju + Pokreni glavni player u cjeloekranskom prikazu + Reproduciraj sljedeći + Sljedeći u popisu izvođenja + Prikaži Picassove vrpce u boji na slikama koje označavaju njihov izvor: crvena za mrežu, plava za disk i zelena za memoriju Izbrisano %1$s preuzimanje Izbrisana %1$s preuzimanja @@ -678,37 +678,59 @@ Traženje novih verzija … Prijedlozi lokalne pretrage Traži nove verzije - Nemoj pokretati videa u mini reproduktoru, već se izravno pokreni cjeloekranski prikaz, ako je automatsko okretanje zaključano. Mini reproduktoru i dalje možeš pristupiti izlaskom iz cjeloekranskog prikaza - Novi feedovi + Nemoj pokretati videa u mini playeru, već izravno pokreni cjeloekranski prikaz, ako je automatsko okretanje zaključano. Mini playeru i dalje možeš pristupiti napuštanjem cjeloekranskog prikaza + Novi elementi feeda Obavijest o prijavi greške Obavijesti za prijavu grešaka Stvori obavijest o grešci NewPipe je naišao na grešku, dodirni za prijavu Došlo je do greške, pogledaj obavijest Prekini rad playera - Obavijest reproduktora - Prilagođavanje obavijesti reproduktora + Obavijest playera + Konfiguriraj obavijest trenutačno reproduciranog streama Obavijesti Novi videozapisi - Obavijesti novih streamova pretplaćenih kanala - Želite li izbrisati sve preuzete datoteke\? + Obavijesti novih streamova od pretplaćenih kanala + Želiš li izbrisati sve preuzete datoteke\? Obavijesti su onemogućene - Pretplatili ste se ovome kanalu + Pretplatio/la si se na ovaj kanal , - Uključiti/isključiti sve + Uključi/isključi sve Bilo kakva mreža Obavijesti novih streamova pretplaćenih kanala - Pokaži zalogajnicu greške - Učitavanje pojedinosti streama… - Pokrenite provjeru novih streamova - Učestalost provjere - LeakCanary nije dostupno + Prikaži kratku poruku greške + Učitavanje pojedinosti streama … + Pokreni traženje novih streamova + Prvjeravanje učestalosti + Biblioteka „LeakCanary” nije dostupna Podešavanje visine tona po glazbenim polutonovima Obavijesti o novim streamovima Potrebna mrežna veza Zadano za ExoPlayer - Primite obavijesti - Za ovu radnju nije pronađen odgovarajući upravitelj datoteka. -\nMolimo vas da instalirate upravitelj za datoteke ili da pokušate onemogućiti \'%s\' u postavkama preuzimanja. + Primaj obavijesti + Za ovu radnju nije pronađen odgovarajući upravljač datoteka. +\nInstaliraj upravljač datoteka ili pokušaj onemogućiti „%s” u postavkama preuzimanja. Prikvačeni komentar + Prikazuje opciju prekida rada kad se player koristi + Prikaži „Prekini rad playera” + ExoPlayer standard + Posto + Poluton + Streamovi koje program za preuzimanje još ne podržava se ne prikazuju + Vanjski playeri ne podržavaju odabrani stream + Promijeni veličinu intervala učitavanja (trenutačno %s). Niža vrijednost može ubrzati početno učitavanje videa. Promjene zahtijevaju ponovno pokretanje playera. + + %s novi stream + %s nova streama + %s novih streamova + + Veličina intervala učitavanja reprodukcije + Nepoznat format + Nepoznata kvaliteta + Nijedan stream audiosnimaka nije dostupan za vanjske playere + Nijedan video stream nije dostupan za vanjske playere + Odaberi kvalitetu za vanjske playere + Prikaži buduća videa + Za ovu radnju nije pronađen odgovrajući upravljač datoteka. +\nInstaliraj „Storage Access Framework” kompatibilni upravljač datoteka. \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e25183985..49b472379 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -319,7 +319,7 @@ Eltüntetés Lejátszási lista könyvjelzőzése Egy hasonló videó hozzáadása a befejeződő (nem ismétlődő) lejátszási sorhoz - Sor + Sorba állítás a fájl nem írható felül Az előre- és visszatekerés időtartama Utolsó lejátszási pozíció visszaállítása @@ -362,11 +362,11 @@ Nincs talalat A kiszolgáló nem fogad többszálú letöltést, próbálkozzon újra ezzel: @string/msg_threads = 1 A kiszolgáló nem küld adatokat - Nem lehet csatlakozni a kiszolgálóhoz + A kiszolgáló szerver nem elérhető A kiszolgáló nem található Nem sikerült biztonságos kapcsolatot létesíteni A célmappa nem hozható létre - A fájlt nem lehet létrehozni + A fájlt nem sikerült létrehozni Hiba megjelenítése Ezzel a névvel egy letöltés már várakozik Ezzel a névvel egy letöltés már folyamatban van @@ -683,4 +683,39 @@ Rögzített megjegyzés LeakCanary nem elérhető + Lejátszó értesítés + Módosítsa a betöltési intervallum méretét (jelenleg %s). Az alacsonyabb érték felgyorsíthatja a videó kezdeti betöltését. A változtatásokhoz a lejátszó újraindítása szükséges. + Az aktuális lejátszás konfigurálása értesítés + Értesítések + Új élő közvetítések + Értesítések új élő közvetítésekről a feliratkozott csatornák esetén + Élő közvetítés betöltése.… + Keressen új élő közvetítést + Új élő közvetítés értesítések + Értesítésen új élő közvetítés esetén a feliratkozott csatornákhoz + Ellenőrzési gyakoriság + Szükséges hálózati kapcsolat + Bármilyen hálózat + Törli az összes letöltött fájlt a lemezről\? + Értesítsen + Értesítéstek kikapcsolva + Lejátszási intervallum mérete + Százaléka + + %s új elő közvetítés + %s új elő közvetítések + + ExoPlayer alapértelmezett + Feliratkoztál erre a csatornára + , + Azok az élő adások melyek nem támogatottak a letöltő által, rejtve vannak. + A választott élő adást nem lehet külső lejátszóval lejátszani. + Összes váltása + Külső lejátszók számára nem érhető el az hang csatorna + Külső lejátszók számára nem érhető el videó + Válassz minőséget külső lejátszókhoz + Ismeretlen formátum + Ismeretlen minőség + Félhang + Jövőbeli videók megjelenítése \ 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 10d397aef..218854841 100644 --- a/app/src/main/res/values-hy/strings.xml +++ b/app/src/main/res/values-hy/strings.xml @@ -217,4 +217,7 @@ Հանրային Պիտակներ Ծանուցումները անջատված են + Ոչինչ բացի դատարկությունից + Կրկին փորձել + Հոսքի նորերը \ No newline at end of file diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index d5bc90e6f..f4383defb 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -233,4 +233,16 @@ Solmente alicun apparatos pote reproducer videos 2K/4K Initiar le reproductor principal in schermo plen Solmente le URLs HTTPS es supportate + Repeter + Aleatori + Cargante fe + Privacitate + Licentia + Comenciava le discarga + Private + Aperir le sito web + Per %s + Monstrar le videos futur + Radio + Create per %s \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index e84fb393a..d4d2db5a8 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -121,7 +121,7 @@ Terlepas apakah Anda memiliki ide untuk; terjemahan, perubahan desain, pembersihan kode, atau perubahan kode yang signifikan, segala bantuan akan selalu diterima. Semakin banyak akan semakin baik jadinya! Baca lisensi Kontribusi - Subscribe + Berlangganan Disubscribe Apa Yang Baru Lanjutkan pemutaran @@ -550,7 +550,7 @@ Tombol tindakan ketiga Tombol tindakan kedua Tombol tindakan pertama - Ubah ukuran thumbnail yang ditampilkan di notifikasi dari rasio aspek 16:9 ke 1:1 (mungkin terdistorsi) + Ubah ukuran thumbnail yang ditampilkan di notifikasi dari rasio aspek 16:9 ke 1:1 Ubah ukuran thumbnail ke rasio aspek 1:1 Tampilkan kebocoran memori Ditambahkan @@ -704,4 +704,5 @@ Pilih kualitas untuk pemain eksternal Format tidak diketahui Ukuran interval pemuatan playback + Tampilkan video mendatang \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 96d541d80..c2d0a2c6f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -560,8 +560,8 @@ Casuale Niente Ripeti - Ridimensiona copertina alla proporzione 1:1 - Modifica la proporzione della copertina del video mostrata nella notifica da 16:9 a 1:1 (può introdurre distorsioni) + Ritaglia copertina con proporzione 1:1 + Ritaglia la copertina del video mostrata nella notifica, cambiando la proporzione da 16:9 a 1:1 Mostra memory leak Aggiunto alla coda Accoda @@ -716,5 +716,6 @@ Seleziona qualità per lettori esterni Qualità sconosciuta Formato sconosciuto - Dimensione dell\'intervallo di caricamento della riproduzione + Dimensione intervallo di caricamento della riproduzione + Mostra video futuri \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index e65897de3..223a5f21a 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -14,7 +14,7 @@ 動画を保存するフォルダー ダウンロードした動画をここに保存します 動画ファイルをダウンロードするフォルダーを選択して下さい - デフォルトの解像度 + デフォルトの画質 Kodi で再生 Kore をインストールしますか? 「Kodi で再生」オプションを表示 @@ -24,7 +24,7 @@ ダウンロード 「次の動画」と「関連動画」を表示 対応していないURLです - デフォルトの言語 + デフォルトの言語設定 動画と音声 ビデオ再生、時間: 投稿者アイコンのサムネイル @@ -49,8 +49,8 @@ 保存メニューを設定できませんでした コンテンツ 年齢制限のあるコンテンツを表示 - 申し訳ありません。発生すべきでものではありませんでした。 - メールで不具合を報告 + 申し訳ありません。想定外のエラーが発生しました。 + 不具合をメールで報告 申し訳ありません、不具合が発生しました。 報告 情報: @@ -70,15 +70,15 @@ ファイル名 同時接続数 エラー - NewPipe ダウンロード中 + ダウンロード中 (NewPipe) タップして詳細を表示 お待ちください… クリップボードにコピーしました - 後ほど設定でダウンロードフォルダを定義してください + 後ほど、ダウンロードフォルダーを設定してください ダウンロード ダウンロード 不具合報告 - アプリ/UI がクラッシュしました + アプリ(UI)がクラッシュしました どんな問題:\\nリクエスト:\\nコンテンツ言語:\\nコンテンツ国:\\nアプリ言語:\\nサービス:\\nGMT 時間:\\nパッケージ:\\nバージョン:\\nOSバージョン: reCAPTCHA の要求 reCAPTCHA を要求しました @@ -93,18 +93,18 @@ ポップアップモードで再生中 無効 デフォルトの動画形式 - デフォルトのポップアップ解像度 - より高い解像度を表示 - 一部のデバイスのみ2K/4K動画を再生できます + デフォルトの画質 (ポップアップ表示) + より高い画質を表示 + 2K/4K動画は一部のデバイスでのみ再生できます バックグラウンド ポップアップ 消去 - ポップアップの属性を記憶 - ポップアップしたサイズと位置を記憶します + ポップアップの属性を記憶する + ポップアップのサイズと位置を記憶する 一部の解像度では音声がありません 検索候補の表示 検索時に表示する候補を選択します - 最高の解像度 + 最高 NewPipe について サードパーティー ライセンス © %1$s 作者 %2$s ライセンス %3$s @@ -125,8 +125,8 @@ 新着 検索履歴 検索履歴を記憶します - 視聴履歴 - 再生した履歴を記憶します + 再生履歴 + 再生履歴を記憶します 再生の再開 電話などによる中断の後、再生を再開します プレイヤー @@ -138,9 +138,9 @@ NewPipe の通知 [不明] 動画の再生ができませんでした - 回復不能な再生エラーが発生しました - 何も見つかりませんでした - チャンネル登録なし + 回復不能なエラーが発生しました + 一致する結果はありませんでした + チャンネル登録者なし 動画がありません 保存 ファイル名に使用可能な文字 @@ -178,7 +178,7 @@ データベースをエクスポート 既存の履歴、登録リスト、プレイリストおよび (任意) 設定は上書きされます 再生履歴、登録チャンネル一覧、プレイリストおよび設定をエクスポートします - 再生エラーからの回復中 + エラーから回復中です 外部プレイヤーは、これらのタイプのリンクをサポートしていません エクスポートしました インポートしました @@ -201,7 +201,7 @@ キャッシュを消去 アプリ内のキャッシュデータをすべて削除します キャッシュが消去されました - 次のを自動でキューに追加する + 次の動画を自動でキューに追加する デバッグ ファイル 動画が見つかりません @@ -255,7 +255,7 @@ プライバシーポリシーを確認 おおまかなシーク おおまかなシークを使用することで精度が下がる代わりに高速にシークができます。5 秒、15 秒または 25 秒間隔のシークはできません - すべてのサムネイルの読み込みと保存を無効化します。このオプションを切り替えるとメモリおよびディスク上の画像キャッシュが消去されます + サムネイルの読み込みと保存を無効化します。(このオプションを切り替えるとメモリとディスク上の画像キャッシュが消去されます) キューに関連動画を追加して再生を続ける (繰り返ししない場合) すべての再生履歴を削除しますか? すべての検索履歴を削除しますか? @@ -276,11 +276,11 @@ 最も再生された動画 拡大 プレイリスト - 「長押ししてキュー」のヒントを表示 + 「長押しでキューに追加」のヒントを表示 トラック NewPipe のプレイヤーの通知 新着と人気 - 長押ししてキューに追加 + 長押しでキューに追加 ポップアップで連続再生を開始 お好みの「開く」アクション コンテンツを開くときのデフォルト動作 — %s @@ -302,7 +302,7 @@ 同意する 拒否する 制限なし - モバイルデータ使用時の解像度の制限 + モバイルネットワーク使用時の画質 アプリ切り替え時の最小化 プレイヤーから他のアプリに切り替え時の動作 — %s 何もしない @@ -345,7 +345,7 @@ NewPipe のアップデートがあります! タップでダウンロード 完了 - 保留中 + 順番に処理中 一時停止 順番待ちに追加しました 保存処理をしています @@ -397,7 +397,7 @@ 再生位置を削除しました ファイルが移動または削除されました ファイルを上書きできません - この名前の保留中のダウンロードがあります + 同じファイル名のダウンロードが既に進行中です ファイルの作業中に NewPipe が閉じられました デバイスに空き容量がありません ファイルが削除されたため、進行状況が失われました @@ -428,7 +428,7 @@ %s 人が聴取中 アプリを再起動すると、言語が変更されます - 高速早送り/巻き戻し時間 + 高速早送り/巻き戻し間隔 PeerTube インスタンス PeerTube インスタンスを選択する あなたに最適なインスタンスを探す: %s @@ -531,7 +531,7 @@ プレイリスト ページ プレイリストを選択してください 自動的に再生を開始します — %s - 自動キュー + 自動でキューに追加 アクティブなプレイヤーのキューが入れ替わります プレイヤーを別のプレイヤーに切り替えるとキューが置き換わる可能性があります しない @@ -539,8 +539,8 @@ キューを再生 キューを消去する前に確認する URL を認識できませんでした。他のアプリで開きますか? - 通知に表示される動画サムネイルを 16:9 から 1:1 のアスペクト比にスケールします (ゆがみが生じるかもしれません) - サムネイルを 1:1 のアスペクト比にスケールする + 通知に表示されるサムネイルを 16:9 から正方形にします + サムネイルを正方形にする 以下をタップして通知のアクションを編集します。右側にあるチェックボックスを使用してコンパクトな通知に表示するものを 3 つまで選択します コンパクトな通知に表示されるアクションは 3 つまで選ぶことができます! 5 番目のアクションボタン @@ -569,8 +569,8 @@ 動画のハッシュ化通知 動画のハッシュ化進行状況の通知 コメント - 無効にするとビデオの概要と追加情報を非表示にします - 説明を表示 + 無効にすると動画の概要欄と追加情報を非表示にします + 概要欄を表示 最近 開く 説明 @@ -612,7 +612,7 @@ サムネイルの URL ウェブサイトを開く ダウンロードのたびに保存する場所を尋ねます - ダウンロードフォルダがまだ設定されていません。今すぐデフォルトのフォルダを選択してください + ダウンロードフォルダーがまだ設定されていません。今すぐデフォルトのフォルダーを選択してください Android 10 以降は \'Storage Access Framework\' のみがサポートされます 高速モードでこの情報の詳細は提供されません。 \'%s\' のフィードを読み込めませんでした。 @@ -633,13 +633,13 @@ 低品質 (小) 高品質 (大) シークバーのサムネイルプレビュー - コメントは無効です + コメントは無効になっています 視聴済みとしてマーク リモート検索候補 ローカル検索候補 アイテムをスワイプして削除 直接フルスクリーンモードに切り替えて、ミニプレイヤーで動画を開始しません。自動回転がロックされている場合でも、フルスクリーンを終了することでミニプレイヤーにアクセスできます - フルスクリーンでメインプレイヤーを開始 + プレイヤーをフルスクリーンで開始 %1$s つのダウンロードを削除しました @@ -660,7 +660,7 @@ 新しいフィードアイテム エラー報告通知 エラーが発生しました。通知をご覧ください - NewPipe はエラーに遭遇しました。タップして報告 + エラーが発生しました (タップすると報告できます) スナックバーにエラーを表示 固定されたコメント この動作に適切なファイルマネージャが見つかりませんでした。 @@ -691,9 +691,18 @@ 新しいストリーム 通知 現在再生しているストリームの通知を構成 - 読み込む間隔を変更します (現在 %s)。小さい値にすると初回読み込み時間が短くなります。変更にはプレイヤーの再起動が必要です。 + 読み込み間隔を変更します (現在 %s)。小さくすると再生開始までの時間が短くなります。変更を適用するには再起動が必要です。 必要なネットワークの種類 パーセント 半音 すべてのネットワーク + データの読み込み間隔 + 未知の形式 + 未知の品質 + サポートされてない動画は表示されていません + 選択された動画は外部プレイヤーではサポートされていません + 外部プレイヤーで利用できる音声情報がありません + 外部プレイヤーで利用できる映像情報がありません + 外部プレイヤーでの品質を選択 + 次の動画を表示する \ No newline at end of file diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 0e2cf6962..9e4d29cfe 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -539,7 +539,7 @@ ഫോർമാറ്റുചെയ്‌ത റിപ്പോർട്ട് പകർത്തുക പ്ലേലിസ്റ്റ് പേജ് GitHub- ൽ റിപ്പോർട്ട് ചെയ്യുക - ലഘുചിത്രം 1: 1 വീക്ഷണാനുപാതത്തിലേക്ക് സ്കെയിൽ ചെയ്യുക + ലഘുചിത്രം 1: 1 വീക്ഷണാനുപാതത്തിലേക്ക് ക്രോപ് ചെയ്യുക വീഡിയോ കവർ ചിത്രത്തിന്റെ പ്രധാന നിറത്തിന് അനുസരിച്ചു നോട്ടിഫിക്കേഷന്റെ കളർ മാറ്റാൻ ആൻഡ്രോയ്ഡിനെ അനുവദിക്കുക (ഇത് എല്ലാം ഉപകരണങ്ങളിലും ലഭ്യമല്ല ) നോട്ടിഫിക്കേഷൻ വർണ്ണാഭമാകുക ഒന്നുമില്ല @@ -547,7 +547,7 @@ ആവർത്തിക്കുക കോം‌പാക്റ്റ് അറിയിപ്പിൽ‌ കാണിക്കുന്നതിന് നിങ്ങൾക്ക് പരമാവധി മൂന്ന് പ്രവർ‌ത്തനങ്ങൾ‌ തിരഞ്ഞെടുക്കാനാകും! ഇതിനോടൊപ്പം തുറക്കുക - നോട്ടിഫിക്കേഷനിൽ കാണിക്കുന്ന വീഡിയോ കവർ ചിത്രം 16:9 എന്ന അനുപാതത്തിൽ നിന്നും 1:1 ലേക്ക് മാറ്റാം (പ്രശ്നങ്ങൾ ഉണ്ടാവാൻ സാധ്യത ) + നോട്ടിഫിക്കേഷനിൽ കാണിക്കുന്ന വീഡിയോ കവർ ചിത്രം 16:9 എന്ന അനുപാതത്തിൽ നിന്നും 1:1 ലേക്ക് ക്രോപ് ചെയ്യുക ഡൗൺലോഡ് ആരംഭിച്ചു ചുവടെ നിങ്ങളുടെ പ്രിയപ്പെട്ട രാത്രി തീം തിരഞ്ഞെടുക്കാം നിങ്ങളുടെ പ്രിയപ്പെട്ട രാത്രി തീം തിരഞ്ഞെടുക്കുക — %s diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index fd80a7d54..c6d7697c5 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -709,4 +709,12 @@ Je bent nu geabonneerd op dit kanaal Alle gedownloade bestanden van schijf wissen\? + De gekozen stream wordt niet ondersteund door externe spelers + Er zijn geen videostreams beschikbaar voor externe spelers + Onbekende kwaliteit + Streams die nog niet kunnen worden opgeslagen, worden niet getoond + Geen audiostreams beschikbaar voor externe spelers + Kies kwaliteit voor externe spelers + Onbekend bestandstype + Intervalgrootte tijdens afspelen \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 283c1f223..a009023d5 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -575,8 +575,8 @@ Przycisk trzeciej akcji Przycisk drugiej akcji Przycisk pierwszej akcji - Skaluj miniaturę wideo wyświetlaną w powiadomieniu z proporcji 16:9 do 1:1 (może powodować zniekształcenia) - Skaluj miniaturę do proporcji 1:1 + Przycinaj miniaturę wideo wyświetlaną w powiadomieniu z proporcji 16:9 do 1:1 + Przycinaj miniaturę do proporcji 1:1 Dodano do kolejki Dodaj do kolejki Pokaż wycieki pamięci @@ -738,4 +738,5 @@ Wybierz jakość dla zewnętrznych odtwarzaczy Nieznany format Nieznana jakość + Pokaż przyszłe wideo \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 0a4b2c2ad..9d19a8585 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -560,8 +560,8 @@ Terceiro botão de ação Segundo botão de ação Primeiro botão de ação - Dimensionar a miniatura do vídeo mostrada na notificação da proporção 16:9 para 1:1 (pode apresentar distorções) - Dimensionar a miniatura para a proporção de 1:1 + Cortar a miniatura do vídeo mostrada na notificação da proporção 16:9 para 1:1 + Cortar a miniatura para a proporção de 1:1 Mostrar vazamentos de memória Na fila Pôr na fila @@ -717,4 +717,5 @@ Formato desconhecido Qualidade desconhecida Tamanho do intervalo de carregamento da reprodução + Mostrar vídeos futuros \ No newline at end of file diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 4f3559eeb..1f6b75501 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -716,4 +716,6 @@ Formato desconhecido Qualidade desconhecida Selecione a qualidade para reprodutores externos + Tamanho do intervalo de carregamento da reprodução + Mostrar vídeos futuros \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index b1f579298..86306377f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -544,8 +544,8 @@ Terceiro botão de ação Segundo botão de ação Primeiro botão de ação - Ajustar miniatura de vídeo mostrada na notificação de 16:9 para 1:1 (pode haver distorções) - Ajustar miniatura à proporção de 1:1 + Cortar a miniatura de vídeo mostrada na notificação de 16:9 para 1:1 + Cortar miniatura na proporção 1:1 Iniciar reprodução automaticamente — %s Reproduzir fila Nunca @@ -716,4 +716,6 @@ Formato desconhecido Qualidade desconhecida A transmissão selecionada não é suportada por reprodutores externos + Mostrar vídeos futuros + Tamanho do intervalo de carregamento da reprodução \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 58a38ea6e..52c1ef5a3 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -378,8 +378,8 @@ Al treilea buton de acțiune Al doilea buton de acțiune Primul buton de acțiune - Scalați miniatura video afișată în notificare de la raportul de aspect 16:9 la 1:1 (poate introduce distorsiuni) - Scalare miniatură la raport aspect 1:1 + Tăiați miniatura video afișată în notificare de la raportul de aspect 16:9 la 1:1 (poate introduce distorsiuni) + Tăiere miniatură la raportul de aspect 1:1 Se arată rezultate pentru:%s Nicio aplicație de pe dispozitivul dvs. nu poate deschide acesta Capitole @@ -722,4 +722,13 @@ V-ați abonat la acest canal Comutați toate , + Fluxurile care încă nu pot fi descărcate nu sunt afișate + Fluxul selectat nu este acceptat de playerele externe + Nu sunt disponibile fluxuri audio pentru playerele externe + Nu sunt disponibile fluxuri video pentru playerele externe + Selectați calitatea pentru playerele externe + Format necunoscut + Calitate necunoscută + Dimensiunea intervalului de încărcare de redare + Afișați videoclipurile din viitor \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c91368c2a..50e0b4d48 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -718,7 +718,7 @@ Закреплённый комментарий LeakCanary недоступна Стандартное значение ExoPlayer - Изменить размер интервала загрузки (сейчас %s). Меньшее значение может ускорить начальную загрузку видео. Изменение значения потребует перезапуска плеера. + Изменить размер предварительной загрузки (сейчас %s). Меньшее значение может ускорить загрузку видео. При изменении требуется перезапуск плеера. Загрузка деталей трансляции… Проверить на наличие новых трансляций Удалить все загруженные файлы\? @@ -733,4 +733,6 @@ Нет ни одного доступного аудио потока для внешних проигрывателей Выберите качество для внешних проигрывателей Неизвестное качество + Размер предварительной загрузки + Показывать будущие видео \ No newline at end of file diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 81a71402e..d3a92b116 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -550,7 +550,7 @@ Pedi una cunfirma in antis de iscantzellare una lista Òrdine casuale Modìfica cada atzione de notìfica inoghe in suta incarchende·la. Ischerta·nde finas a tres de ammustrare in sa notìfica cumpata impreende sas casellas de controllu a destra - Iscala sa miniadura ammustrada in sa notìfica dae su formadu in 16:9 a cussu 1:1 (diat pòdere causare istorchimentos) + Sega sa miniadura ammustrada in sa notìfica dae su formadu in 16:9 a cussu 1:1 Nudda Carrighende Repite @@ -560,7 +560,7 @@ Su de tres butones de atzione Su de duos butones de atzione Su de unu butone de atzione - Pone in iscala sa miniadura in formadu 1:1 + Sega sa miniadura in formadu 1:1 Ammustra sas pèrdidas de memòria Annànghidu a sa lista Pone in lista @@ -716,4 +716,6 @@ Formadu disconnotu Calidade disconnota Su flussu seletzionadu no est galu suportadu dae letores esternos + Mannària de s\'intervallu de carrigamentu de sa riprodutzione + Ammustra sos vìdeos imbenientes \ No newline at end of file diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 4645fcfff..3d7888432 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -722,4 +722,12 @@ Poltón Nahrávanie podrobností streamu… Percent + Vybraný stream nie je podporovaný externými prehrávačmi + Žiadne audio streamy nie sú k dispozícií pre externé prehrávače + Vybrať kvalitu pre externé prehrávače + Neznámy formát + Interval medzipamäte + Streamy nepodporované sťahovačom sa nezobrazujú + Žiadne video streamy nie sú k dispozícií pre externé prehrávače + Neznáma kvalita \ No newline at end of file diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index d12b1e4d4..431ef57ad 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -19,7 +19,7 @@ Batoonka hawsha sadexaad Batoonka hawsha labaad Batoonka hawsha koowaad - La ekaysii galka muuqaalka xaga ogaysiisyada ka muuqda cabirka 1:1 ayadoo laga soo baddalayo 16:9 (wuxuu keeni karaa shucaac) + La ekaysii galka muuqaalka xaga ogaysiisyada ka muuqda cabirka 1:1 ayadoo laga soo baddalayo 16:9 Galka la ekaysii cabirka 1:1 Soo bandhig istikhyaar ah in muuqaalka lagu furo xarunta madadaalada Kodi Soodhig istikhyaarka \"Ku fur Kodi\" @@ -649,4 +649,6 @@ Tus tilmaamayaasha sawirka Soojeedinada raadinta banaanka Soojeedinada raadinta gudaha + Cabirka soodaarida udhexeeya + Jabi Daareha \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index edcc13b08..daf2a4485 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -524,9 +524,9 @@ Skapad av %s Ingen spellista har bokmärkts än Visa endast prenumerationer som inte grupperats - Skala miniatyrbild till 1: 1 bildförhållande + Beskär miniatyrbild till 1: 1 bildförhållande Endast över Wi-Fi - Skala videominiatyrbilden som visas i aviseringen från 16:9- till 1:1-förhållande (kan orsaka bildförvrängning) + Beskär videominiatyrbilden som visas i aviseringen från 16:9 till 1:1 bildförhållande Starta uppspelning automatiskt — %s Uppspelningskö Kunde inte känna igen URL:en. Vill du öppna med annan app\? @@ -713,4 +713,9 @@ Okänd kvalitet Inga ljudströmmar tillgängliga för externa spelare Okänt format + Videoströmmar som ännu inte stöds av nedladdaren visas inte + Inläsningsintervalls storlek + Välj kvalitet för externa spelare + Visa framtida videor + Den valda videoströmmen stöds inte av externa spelare \ No newline at end of file diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml index 3958e02b0..22ad6fbf5 100644 --- a/app/src/main/res/values-te/strings.xml +++ b/app/src/main/res/values-te/strings.xml @@ -425,4 +425,29 @@ స్వయంచాలకంగా రూపొందించబడింది (ఎక్కించినవారు కనబడుటలేదు) స్వయంచాలకంగా రూపొందించబడింది శీర్షికలు + సభ్యత్వాల కోసం కొత్త స్ట్రీమ్‌ల గురించి నోటిఫికేషన్‌లు + స్థితి పునరుద్ధరణ + లయ + కొత్త స్ట్రీమ్స్ + + %s కొత్త స్ట్రీమ్ + %s కొత్త స్ట్రీమ్స్ + + స్ట్రీమ్ వివరాలను లోడ్ చేస్తోంది… + మెమరీ లీక్‌లను చూపించు + జీవితచక్రం లేని లోపాలను నివేదించండి + ఈ చర్య నెట్‌వర్క్ ఖరీదైనదని గుర్తుంచుకోండి. +\n +\nమీరు కొనసాగించాలనుకుంటున్నారా\? + శాతం + అర్ధరాగం + ప్లేబ్యాక్ లోడ్ విరామం పరిమాణం + వస్తువులపై అసలు క్రిత సమయాన్ని చూపుము + పారవేయడం తర్వాత ఫ్రాగ్మెంట్ లేదా యాక్టివిటీ లైఫ్‌సైకిల్ వెలుపల బట్వాడా చేయలేని Rx మినహాయింపులను బలవంతంగా నివేదించడం + మెమరీ లీక్ మానిటరింగ్ హీప్ డంపింగ్ చేసినప్పుడు యాప్ స్పందించక పోవడానికి కారణం కావచ్చు + శృతి + అన్‌హుక్ (వక్రీకరణకు కారణం కావచ్చు) + అడుగు + నిశ్శబ్ద సమయంలో వేగంగా ముందుకు వెళ్లుము + ప్లేబ్యాక్ స్పీడ్ నియంత్రణలు \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 0986fd74b..072b92193 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,8 +1,8 @@ - Başlamak için büyütece dokunun. + Başlamak için büyütece dokun. %1$s tarihinde yayınlandı - Akış oynatıcısı bulunamadı. VLC yüklensin mi\? + Akış oynatıcısı bulunamadı. VLC kurulsun mu\? Yükle İptal Tarayıcıda aç @@ -13,14 +13,14 @@ Bunu mu demek istediniz: \"%1$s\"\? Şununla paylaş Dış video oynatıcı kullan - Dış ses oynatıcı kullanın + Dış ses oynatıcı kullan Video indirme klasörü İndirilen video dosyaları burada depolanır Video dosyaları için indirme klasörünü seç Ses indirme klasörü İndirilen ses dosyaları burada depolanır Ses dosyaları için indirme klasörünü seç - Varsayılan çözünürlük + Öntanımlı çözünürlük Kodi ile oynat Eksik Kore uygulaması yüklensin mi\? \"Kodi ile oynat\" seçeneğini göster @@ -103,7 +103,7 @@ Açılan pencerenin son boyutunu ve konumunu hatırla Bazı çözünürlüklerde sesi kaldırır Arama önerileri - Arama yaparken gösterilecek önerileri seçin + Ararken gösterilecek önerileri seç En iyi çözünürlük NewPipe Hakkında Üçüncü Taraf Lisansları @@ -130,7 +130,7 @@ Abonelikler Yenilikler Arama geçmişi - Arama sorgularını yerel olarak saklayın + Arama sorgularını yerel olarak sakla İzleme geçmişi İzlenen videoların kaydını tut Oynatmayı sürdür @@ -191,7 +191,7 @@ Ana Görünüme Geç Çekmeceyi Aç Çekmeceyi Kapat - Akış oynatıcı bulunamadı (Oynatmak için VLC yükleyebilirsiniz). + Akış oynatıcı bulunamadı (Oynatmak için VLC kurabilirsiniz). Her Zaman Yalnızca Bir Kez Dış oynatıcılar bu tür bağlantıları desteklemez @@ -560,8 +560,8 @@ Üçüncü eylem düğmesi İkinci eylem düğmesi Birinci eylem düğmesi - Bildirimde gösterilen video küçük resmini 16:9\'dan 1:1 en/boy oranına ölçeklendir (bozulmalara neden olabilir) - Küçük resmi 1:1 en/boy oranına ölçeklendir + Bildirimde gösterilen video küçük resmini 16:9\'dan 1:1 en boy oranına kırp + Küçük resmi 1:1 en boy oranına kırp Bellek sızıntılarını göster Sıraya eklendi Kuyruğa ekle @@ -578,7 +578,7 @@ Video dosya özetleme süreci için bildirimler Video dosya özeti bildirimi En Son - Akış oluşturucu, akış içeriği veya bir arama isteği hakkında ek bilgiler içeren meta bilgi kutularını gizlemek için kapatın + Akış oluşturucu, akış içeriği veya arama isteğiyle ilgili ek bilgiler içeren üst veri bilgi kutularını gizlemek için kapat Üst bilgiyi göster Bölümler Açıklama @@ -693,7 +693,7 @@ Yeni akışları denetlemeyi çalıştır Oynatıcı bildirimi - Oynatılan akış bildirimini yapılandırın + Oynatılan akış bildirimini yapılandır Yeni akışlar Akış ayrıntıları yükleniyor… Abonelikler için yeni akışlarla ilgili bildirimler @@ -701,7 +701,7 @@ Bildirimler devre dışı Bildirim alın Artık bu kanala abone oldunuz - Aboneliklerden yeni akışlar hakkında bildirim gönder + Aboneliklerden yeni akışlarla ilgili bildirim gönder Denetleme sıklığı Herhangi bir ağ İndirilen tüm dosyalar diskten silinsin mi\? @@ -709,12 +709,13 @@ Tümünü değiştir Yüzde Ara ton - Seçilen yayın harici oynatıcılar tarafından desteklenmiyor - İndirici tarafından henüz desteklenmeyen yayınlar gösterilmez - Harici oynatıcılar için ses yayını yok - Harici oynatıcılar için video yayını yok - Harici oynatıcılar için kalite seçin + Seçilen akış dış oynatıcılarca desteklenmiyor + İndiricice henüz desteklenmeyen akışlar gösterilmez + Dış oynatıcılar için ses akışı yok + Dış oynatıcılar için video akışı yok + Dış oynatıcılar için nitelik seç Bilinmeyen biçim - Bilinmeyen kalite + Bilinmeyen nitelik Oynatma yükleme aralığı boyutu + Gelecekteki videoları göster \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 736a49c8e..835c87f37 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -558,7 +558,7 @@ Кнопка третьої дії Кнопка другої дії Кнопка першої дії - Збільшити ескіз до масштабу 1:1 + Обрізати ескіз до пропорцій 1:1 Відкрити через Завантаження почалося Виберіть нічну тему — %s @@ -616,7 +616,7 @@ Кольорове сповіщення У компактному сповіщенні є не більше трьох дій! Дії можна змінити, натиснувши на них. Позначте не більше трьох для показу в компактному сповіщенні - Масштабувати мініатюру відео 16: 9 до 1:1 (можливі спотворення) + Обрізати мініатюру відео показувану в сповіщенні з пропорцій 16: 9 до 1:1 Вимкнення тунелювання медіаданих за наявності чорного екрана або гальмування під час відтворення відео Вимкнути тунелювання медіа «Фреймворк доступу до сховища» (SAF) підтримується лише починаючи з Android 10 @@ -734,4 +734,5 @@ Виберіть якість для зовнішніх програвачів Невідома якість Розмір інтервалу завантаження відтворення + Показати наступні відео \ No newline at end of file diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index b58f0946d..2c3582209 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -1,6 +1,6 @@ - شروع کرنے کے لیے \"تلاش\" پر ٹیپ کریں + شروع کرنے کے لیے \"کلاں نما شیشہ\" پر ٹیپ کریں %1$s کو شائع ہوا انسٹال منسوخ کریں @@ -494,8 +494,10 @@ تیسرا ایکشن بٹن دوسرا ایکشن بٹن پہلا ایکشن بٹن - نوٹیفیکیشن میں دکھائے جانے والے ویڈیو تھمب نیل کو 16: 9 سے 1:1 پہلو تناسب (شاید بگاڑ پیدا ہوسکتا ہے) میں اسکیل کریں + نوٹیفیکیشن میں دکھائے جانے والے ویڈیو تھمب نیل کو 16: 9 سے 1:1 پہلو تناسب میں اسکیل کریں تھمب نیل کو 1:1 کی تناسب میں رکھیں %s کے لئے نتائج دکھا رہا ہے کے ساتھ کھولیں + ویڈیو پلیئر کو کریش کریں + دیکھے ہوئے کو نشان لگائیں \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 2346a4cf9..3a933ef98 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -67,13 +67,13 @@ Nội dung không khả dụng Không thể thiết lập menu tải về Ứng dụng / Giao diện người dùng bị lỗi - Ai Da, NewPipe đã gặp lỗi. Tôi lấy làm tiếc cho bạn. + :( Lmao, app đã xảy ra lỗi. Hãy lướt xuống dưới để xem lỗi. Báo lỗi qua email Xin lỗi, đã xảy ra sự cố. Báo cáo Thông tin: Chuyện gì đã xảy ra: - Cái gì:\\nYêu cầu:\\nNgôn ngữ của nội dung:\\nVùng miền (quốc gia) của nội dung:\\nNgôn ngữ của ứng dụng:\\nDịch vụ:\\nThời gian GMT:\\nTên gói:\\nPhiên bản:\\nPhiên bản hệ điều hành: + Loại lỗi:\\nYêu cầu:\\nNgôn ngữ của nội dung:\\nVùng miền (quốc gia) của nội dung:\\nNgôn ngữ của ứng dụng:\\nDịch vụ:\\nThời gian GMT:\\nTên gói:\\nPhiên bản:\\nPhiên bản hệ điều hành: Nhận xét của bạn (bằng tiếng Anh): Chi tiết: Xem video, thời lượng: @@ -83,7 +83,7 @@ Video Âm thanh Thử lại - ngàn + nghìn triệu tỉ Bắt đầu @@ -96,7 +96,7 @@ Lỗi NewPipe đang tải xuống Chạm để biết chi tiết - Xin hãy đợi… + Đợi chút xíu nha… Đã sao chép vào clipboard Hãy chọn một thư mục để tải xuống trong phần cài đặt Chế độ popup cần quyền này @@ -108,10 +108,10 @@ © %1$s bởi %2$s dưới %3$s Thông tin Giấy phép - Trình phát nội dung nhẹ và mã nguồn mở cho Android. + Trình phát video nhẹ và mã nguồn mở cho Android. Xem trên GitHub Giấy phép của NewPipe - Sự đóng góp luôn được hoan nghênh – cho dù bạn dịch, có ý tưởng thiết kế, dọn code, hay thay đổi rất nhiều phần code. Càng làm nhiều thì ứng dụng này sẽ càng tốt! + Sự đóng góp của bạn luôn được hoan nghênh – kể cả khi bạn dịch, thay đổi giao diện, dọn code hay thay đổi những thứ khác, sự giúp đỡ của bạn vẫn đáng được trân trọng. Bạn càng làm nhiều, ứng dụng này sẽ càng tốt hơn bao giờ hết (Miễn đừng dịch vớ vẩn là được, nhé :]] )! Đọc giấy phép Đóng góp Ngôn ngữ nội dung ưu tiên @@ -169,7 +169,7 @@ Chuyển sang Main Nhập cơ sở dữ liệu Xuất cơ sở dữ liệu - Ghi đè lịch sử, danh sách đăng ký, playlist (và cài đặt, nếu có chọn) hiện tại của bạn + Ghi đè lịch sử, kênh đăng ký, playlist hiện tại (và cài đặt, nếu có) của bạn Xuất lịch sử, danh sách đăng ký, playlist và cài đặt Xóa lịch sử xem Xóa lịch sử các luồng đã phát và vị trí phát @@ -179,7 +179,7 @@ Xóa lịch sử của từ khóa tìm kiếm Xóa toàn bộ lịch sử tìm kiếm\? Đã xóa lịch sử tìm kiếm - Không thể phát luồng này + Không thể phát video này Đã xảy ra lỗi trình phát không thể khôi phục Phục hồi lại trình phát bị lỗi Trình phát ngoài không hỗ trợ các loại liên kết này @@ -191,7 +191,7 @@ Tên tệp không được để trống Đã xảy ra lỗi: %1$s Không có luồng nào để tải về - Không có gì ở đây + Không có gì cả Kéo để sắp xếp lại Không có người đăng ký @@ -217,8 +217,8 @@ Hầu hết các ký tự đặc biệt Không có ứng dụng nào được cài đặt để phát tệp này Đóng góp - NewPipe được phát triển bởi các tình nguyện viên dành thời gian mang lại cho bạn trải nghiệm tốt nhất. Đóng góp một tách cà phê để giúp các nhà phát triển làm NewPipe tốt hơn nữa. - Trả lại + NewPipe được phát triển bởi các tình nguyện viên dành thời gian và tâm huyết của mình để mang lại cho bạn trải nghiệm tốt nhất. Đóng góp một chai nước suối để giúp chúng tôi làm NewPipe tốt hơn nữa. + Ủng hộ một chai nước suối Trang web Truy cập trang web NewPipe để biết thêm thông tin và tin tức. Chính sách bảo mật của NewPipe @@ -244,8 +244,8 @@ Cảnh báo: Không thể nhập tất cả các tệp. Thao tác này sẽ ghi đè cài đặt hiện tại của bạn. Bạn cũng muốn nhập cài đặt? - Thịnh hành - Mới và hot + Thịnh hành :D + Mới và đang hot Loại bỏ Chi tiết Cài đặt âm thanh @@ -292,7 +292,7 @@ Xuất trước Không thể nhập đăng ký Không thể xuất đăng ký - Nhập danh sách đăng ký YouTube từ một bản Google Takeout: + Nhập danh sách đăng ký YouTube từ Google Takeout: \n \n1. Vào URL này: %1$s \n2. Đăng nhập khi được yêu cầu @@ -307,14 +307,14 @@ \n2. Truy cập URL này: %1$s \n3. Đăng nhập khi được hỏi \n4. Sao chép URL tiểu sử mà bạn đã được chuyển hướng đến. - Hãy nhớ rằng hoạt động này có thể khiến bạn bị trừ tiền. + Hãy nhớ rằng hoạt động này có thể khiến bạn bị mất kênh bạn đã đăng ký trước đó. \n \nBạn có muốn tiếp tục không\? Điều khiển tốc độ phát lại - Speed - Chiều cao - Bỏ gắn (có thể gây méo) - Tua đi nhanh trong khi im lặng + Tốc độ + Độ cao + Bỏ gắn (có thể gây méo nhưng vui) + Tua nhanh trong im lặng Tiếp theo Đặt lại Để tuân thủ Quy định bảo vệ dữ liệu chung của châu Âu (GDPR), chúng tôi sẽ thu hút sự chú ý của bạn đến chính sách bảo mật của NewPipe. Vui lòng đọc kỹ. @@ -322,7 +322,7 @@ Chấp nhận Từ chối Không giới hạn - Giới hạn độ phân giải khi sử dụng dữ liệu di động + Giới hạn độ phân giải khi sử dụng 3G, 4G Thu nhỏ khi chuyển qua ứng dụng khác Hành động khi chuyển sang ứng dụng khác từ trình phát chính — %s Không @@ -354,7 +354,7 @@ Danh sách Lưới Tự động - Đã có bản cập nhật NewPipe! + Đã có bản cập nhật mới! Nhấn để tải về Xong đã tạm dừng @@ -438,14 +438,14 @@ Video đã xem trước và sau khi được thêm vào playlist sẽ bị xóa. \nBạn có chắc không\? Video sẽ không thể hồi phục được! Xóa video đã xem\? - Xóa đã xem + Xóa video đã xem Mặt định hệ thống Ngôn ngữ ứng dụng \'Storage Access Framework\' cho phép tải về thẻ SD Sử dụng trình chọn thư mục của hệ thống (SAF) Xóa file đã tải về Xóa lịch sử tải về - Không thể khôi phục bản download này + Không thể khôi phục bản tải xuống này Bật tiếng Tắt tiếng Yêu thích nhất @@ -503,8 +503,8 @@ Có thể được với một số dịch vụ, thường sẽ nhanh hơn nhưng có thể bị giới hạn nội dung nhận được hoặc nội dung nhận được không đầy đủ (v.d. thời lượng, trạng thái,..) Luôn cập nhật Khoảng thời gian kể từ lần cuối cập nhật thông tin kênh trước khi nó được coi là hết hạn — %s - Ngưỡng thời gian cập nhật feed - Nguồn cấp (feed) + Ngưỡng thời gian cập nhật thông báo + Thông báo (feed) Tạo mới Bạn muốn xóa nhóm kênh này\? Tên nhóm kênh trống @@ -516,7 +516,7 @@ Đang xử lý thông báo… Số kênh không tải được: %d Đang tải thông báo… - Feed cập nhật lần cuối vào: %s + Thông báo cập nhật lần cuối vào: %s Do giới hạn của ExoPlayer, khoảng thời gian tua đã được đặt lại thành %d giây đang khôi phục Tự tạo (không tìm thấy người upload) @@ -526,11 +526,11 @@ Chỉ hiện các kênh chưa được nhóm Không bao giờ Chỉ trên Wi-Fi - Hành vi tự động phát — %s + Tự động phát — %s Phát hàng đợi (Video) Không có danh sách nào ở đây Chọn danh sách - Vui lòng kiểm tra xem vấn đề bạn đang gặp đã có báo cáo trước đó chưa. Nếu bạn tạo nhiều báo cáo trùng lặp, bạn sẽ làm tốn thời gian để chúng tôi đọc thay vì thực sự sửa lỗi. + Vui lòng kiểm tra xem vấn đề mà bạn đang gặp đã báo cáo trước đó hay chưa. Nếu bạn tạo quá nhiều báo cáo trùng lặp, bạn sẽ khiến cho chúng tôi tốn thời gian để đọc chúng thay vì sửa lỗi bạn gặp. Báo cáo trên GitHub Sao chép bản báo cáo đã được định dạng Không thể đọc URL này. Mở với app khác\? @@ -548,16 +548,16 @@ Nút hành động thứ tư Nút hành động thứ ba Nút hành động thứ hai - Nút hành đông đầu tiên - Phóng ảnh thu nhỏ của video trong thông báo từ tỉ lệ 16:9 xuống 1:1 (có thể gây méo ảnh) - Phóng ảnh thu nhỏ theo tỉ lệ 1:1 + Nút hành động đầu tiên + Chỉnh ảnh thu nhỏ của video trên thanh thông báo từ tỉ lệ 16:9 thành 1:1 (có thể gây méo ảnh) + Chỉnh ảnh thu nhỏ thành tỉ lệ 1:1 Đang hiện kết quả cho: %s - Hàng - Hiển thị nội dung không an toàn cho trẻ em vì có giới hạn độ tuổi (+18) + Thêm vào danh sách đang phát + Hiển thị nội dung không an toàn cho trẻ em vì có giới hạn độ tuổi (18+) Hiện ảnh thu nhỏ (thumbnail) trên nền màn hình khóa và trong thông báo Xem hình thu nhỏ - Kiểm tra bộ - Hàng + Kiểm tra bộ nhớ + Đã thêm vào danh sách đang phát Xoá Cookie mà NewPipe lưu trữ sau khi bạn vượt Cookie reCAPTCHA đã được xóa Xóa bỏ Cookie của reCAPCHA @@ -621,11 +621,11 @@ Tài khoản đã bị chấm dứt Hiện các mục đã xem Chế độ nguồn dữ liệu nhanh không cung cấp thêm thông tin về cái này. - Tài khoản của tác giả đã bị chấm dứt. + Tài khoản của người này đã bị chấm dứt. \nNewPipe sẽ không thể tải nguồn dữ liệu này trong tương lai. \nBạn có muốn huỷ đăng ký kênh này không\? - Không thể tải nguồn dữ liệu cho \'%s\'. - Lỗi khi tải nguồn dữ liệu + Không thể tải thông báo cho \'%s\'. + Lỗi khi tải nguồn thông báo \'Storage Access Framework\' chỉ được hỗ trợ từ Android 10 trở đi Bạn sẽ được hỏi nơi bạn muốn lưu mỗi mục tải xuống Chưa có thư mục tải xuống nào được đặt, hãy chọn thư mục tải xuống mặc định ngay @@ -633,8 +633,8 @@ Chất lượng thấp (nhỏ hơn) Chất lượng cao (lớn hơn) Xem trước hình thu nhỏ trên thanh tua - Bình luận đang bị tắt - Đã được người tạo thả tim + Bình luận đã bị tắt + Đã được chủ kênh thả \"thính\" Đánh dấu là đã xem Hiện ruy băng được tô màu Picasso ở trên cùng các hình ảnh và chỉ ra nguồn của chúng: đỏ đối với mạng, xanh lam đối với ổ đĩa và xanh lá đối với bộ nhớ Hiện dấu chỉ hình ảnh @@ -650,11 +650,11 @@ Không bắt đầu video ở trình phát mini, mà chuyển trực tiếp thành chế độ toàn màn hình, nếu tự động xoay bị khóa. Bạn vẫn có thể truy cập trình phát mini bằng cách thoát khỏi toàn màn hình Khởi động trình phát chính ở toàn màn hình Đã cho mục tiếp vào hàng đợi - Cho mục tiếp vào hàng đợi + Cho video kế tiếp vào hàng đợi Đang thực hiện...Có thể mất một lúc Thông báo lỗi Thông báo để báo cáo lỗi - NewPipe đã gặp lỗi, nhấn để báo cáo + NewPipe đã gặp sự cố, nhấn để xem và báo cáo Có lỗi xảy ra, hãy xem thông báo Hiện \"làm trình phát dừng\" Hiện tùy chọn dừng đột ngột khi sử dụng trình phát @@ -687,7 +687,7 @@ Thông báo về video mới từ kênh bạn đã đăng ký Thời gian kiểm tra Yêu cầu kết nối mạng - Bất kỳ mạng nào (có thể tính phí) + Bất kỳ loại mạng nào (có thể tính phí) Xóa tất cả tệp đã tải xuống khỏi ổ đĩa\? Thông báo bị tắt Được thông báo @@ -696,4 +696,13 @@ Phần trăm , Nửa cung + Luồng video mà không được trình tải xuống hỗ trợ sẽ không hiện + Không có video khả dụng cho trình chạy ngoài + Video bạn chọn không hỗ trợ trình chạy bên ngoài + Video này không có âm thanh khả dụng cho trình chạy ngoài + Chọn chất lượng cho trình chạy ngoài + Định dạng không xác định (:P) + Độ phân giải không xác định + Kích thước khoảng thời gian tải + Hiện video đề xuất \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 3c307f195..c7f91b7ad 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -550,8 +550,8 @@ 第三操作按钮 第二操作按钮 第一操作按钮 - 将通知中视频缩略图的长宽比从 16:9 强制缩放到 1:1(可能会导致失真) - 强制缩放缩略图至 1:1 比例 + 将通知中视频缩略图的长宽比从 16:9 裁剪到 1:1 + 裁剪缩略图至 1:1 比例 显示内存泄漏 已加入播放队列 加入播放队列 @@ -704,4 +704,6 @@ 没有视频流可用于外部播放器 不显示下载器尚不支持的串流 未知画质 + 回放加载间隔大小 + 显示未来视频 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 9b142a747..9cf2f5ee1 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -142,12 +142,12 @@ 本機搜尋建議 遠端搜尋建議 喺本機儲存搵過嘅查詢 - 睇過嘅紀錄 + 睇過有紀錄 恢復播放 返返最後播放到去嗰個位 清單度睇到播到去邊 - 縮圖放到去 1:1 長寬比 - 顯示喺通知嘅影片縮圖由 16:9 放到去 1:1 長寬比 (話唔定會鬆郁矇) + 縮圖以 1:1 長寬比框起 + 顯示喺通知嘅影片縮圖由 16:9 剪成 1:1 長寬比 通知色彩化 等 Android 根據縮圖嘅主色自訂通知嘅顏色 (注意:唔係部部機都用得) 夜色 @@ -200,7 +200,7 @@ 撈起去浮面 匯入資料庫 主播放器用全螢幕開啟 - 如果自動旋轉鎖定,開啟影片嘅時候唔用袖珍播放器就直接飛去全螢幕模式。您仍可結束全螢幕返返去袖珍播放器 + 開啟影片嘅時候唔用袖珍播放器就直接飛去全螢幕模式,如果自動旋轉鎖定嘅話。您仍可結束全螢幕返返去袖珍播放器 認唔出呢個 URL。要唔要用另一個 app 開? YouTube 提供嘅「嚴格篩選模式」可以過濾潛在嘅成人內容 有年齡限制 (例如 18+) 故可能兒童不宜嘅內容都照顯示 @@ -322,7 +322,7 @@ 100+ 部影片 ∞ 部影片 未開放留言 - 建立 + 加新 未設定下載資料夾,請立即揀選預設嘅下載資料夾 刪除咗 1 個項目。 執執佢 @@ -394,7 +394,7 @@ 下載 課金 匯出唔到訂閱 - 修改播放器字幕啲字嘅大細同背景款式。要重新開過個 app 先會生效 + 修改播放器字幕大細同背景款式。要重新開過個 app 先會生效 執好就撳一下「搞掂」 靜音 處理緊… 可能要等等 @@ -466,7 +466,7 @@ 喺幕後開始播放 部機冇晒位 頂櫳重試幾多次 - 若然有機會用到流動數據嘅時候,或者用得著,雖則有啲下載或者冇得暫停 + 若然有機會用到流動數據嘅時候,可能會用得著,雖則有啲下載或者冇得暫停 輪住下載 內部 私人 @@ -615,7 +615,7 @@ 當喺主影片播放器轉去第個 app 嘅時候點做好 — %s 借過幕後播 借過浮面播 - 自動開啟播放 — %s + 自動開始播放 — %s 上返行人路 時間軸預覽縮圖 載入緊摘要… @@ -644,7 +644,7 @@ 著作者嘅帳戶已經被終止。 \nNewPipe 日後唔會載入到呢個摘要。 \n您要唔要取消訂閱呢個頻道? - 某啲服務會提供,通常快趣好多,但項目數量可能有限兼詳情欠奉 (例如片長、項目類型、直播狀態) + 某啲服務有提供,通常會快趣好多,但項目數量可能有限兼欠奉詳情 (例如片長、項目類型、直播狀態) 剷走播放到邊個位 係咪要全部剷走晒播放到邊個位? 百分比 @@ -662,4 +662,47 @@ 揀選一個站 轉換播放器嘅時候,排隊播可能會清零 NewPipe 係「著佐權」(copyleft) 自由軟件:您可以隨意使用、考究、分享同改進佢。具體而言,您可以依據自由軟件基金會發佈嘅《GNU 通用公眾特許條款》第 3 版或 (按您選擇) 之後任一版本之下嘅條款,重新分發及/或修改呢個軟件。 + 載入相距大細 + 互動頁面 + 預設嘅互動站 + 輸入 URL 或者您嘅 ID 去匯入 SoundCloud 個人檔案: +\n +\n一、喺網頁瀏覽器啟用「桌面版模式」(個網唔支援手機版) +\n二、去呢個網址:%1$s +\n三、叫您就登入 +\n四、複製佢彈返您去個人檔案嗰版個 URL。 + 您個 ID、soundcloud.com/您個id + 揀選互動站 + 顯示返項目原底話時隔幾耐 + 停用多媒體隧道 + 頻道成軍 + 成軍名留空 + 黃袍 + 您係咪要刪除呢個成軍? + 淨係顯示未成軍嘅訂閱 + 啲圖都要騷 Picasso 三色碼顯示源頭:紅碼係網絡上高落嚟,藍碼係儲存喺磁碟本地,綠碼係潛伏喺記憶體中 + 服務原底嘅字會騷返喺串流項目上面 + 影像要推三色碼 + 顯示定預告上畫嘅影片 + 若果播片嘅時候窒下窒下或者黑畫面,就停用多媒體隧道啦 + 點樣用 Google 匯出嚟匯入 YouTube 訂閱: +\n +\n一、去呢個網址:%1$s +\n二、叫您就登入 +\n三、撳一下「包含所有資料」,再撳一下「全部不選」,之後淨係剔返「訂閱」,然後撳「確定」 +\n四、撳一下「下一步」然後揀「建立匯出」 +\n五、個掣騷出嚟嘅時候就撳一下「下載」 +\n六、返返嚟呢度,喺下低撳「匯入檔案」,揀返下載咗嗰個 .zip 檔案 +\n七、[個 .zip 匯入唔到點算好] 將個 .csv 檔案解壓縮抽返出嚟 (通常係擺喺「YouTube and YouTube Music/subscriptions/subscriptions.csv」),喺下低撳「匯入檔案」,揀返抽出嚟個 csv 檔案 + 係咪覺得摘要「懸浮於半路太久,可否再快兩步」?可以試下啟用快速載入 (您可以喺設定度更改,又或者撳一下下低個掣)。 +\n +\nNewPipe 提供兩種載入摘要嘅方針: +\n• 攞晒成個訂閱頻道,慢得嚟志在夠完整。 +\n• 用特設嘅服務終端,快得嚟啲料爭少少。 +\n +\n兩者嘅分別在於,快趣嗰個通常都係爭噉啲料:譬如話項目嘅片長同類型 (分唔到係直播定上載),同埋攞返嚟數目可能會少啲。 +\n +\nYouTube 就係其中一個服務,有用 RSS 摘要提供呢個快趣嘅門路。 +\n +\n所以就睇您點揀:想快定要準。 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 47117ee90..fb908e841 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -550,8 +550,8 @@ 第三動作按鈕 第二動作按鈕 第一動作按鈕 - 將通知中顯示的影片縮圖從 16:9 縮放到 1:1(可能會導致變形) - 把縮圖縮放到 1:1 的長寬比 + 將通知中顯示的影片縮圖從 16:9 裁剪到 1:1 + 將縮圖裁剪為 1:1 的長寬比 顯示記憶體洩漏 已加入佇列 加入佇列 @@ -704,4 +704,5 @@ 沒有可用於外部播放程式的視訊串流 選取外部播放程式的畫質 播放載入間隔大小 + 顯示未來影片 \ No newline at end of file diff --git a/fastlane/metadata/android/az/changelogs/988.txt b/fastlane/metadata/android/az/changelogs/988.txt new file mode 100644 index 000000000..525c0f8cc --- /dev/null +++ b/fastlane/metadata/android/az/changelogs/988.txt @@ -0,0 +1,2 @@ +[YouTube] Videonu başlatmağa başladıqda "Heç bir yayım əldə etmək mümkün olmadı" xətası aradan qaldırıldı +[YouTube] Vide sorğusu əvəzinə "Bu məzmun bu tətbiqdə əlçatan deyil" xətası aradan qaldırıldı diff --git a/fastlane/metadata/android/bn/changelogs/66.txt b/fastlane/metadata/android/bn/changelogs/66.txt new file mode 100644 index 000000000..05aeec04c --- /dev/null +++ b/fastlane/metadata/android/bn/changelogs/66.txt @@ -0,0 +1,33 @@ +# v0.13.7 এর পরিবর্তনসূচী + +### স্থির +- v0.13.6 এর বাছাই করা ফিল্টার সমস্যাগুলি ঠিক করুন + +# v0.13.6 এর পরিবর্তনসূচী + +### উন্নতি + +- বার্গারমেনু আইকন অ্যানিমেশন #1486 অক্ষম করা হয়েছে +- ডাউনলোড #1472 মুছে ফেলা হয়েছে পূর্বাবস্থায় +- শেয়ার মেনু #1498 এ ডাউনলোড এর অপশন +- লং ট্যাপ মেনু #1454 এ শেয়ার অপশন যোগ করা হয়েছে +- প্রস্থান #1354 এ প্রধান প্লেয়ার ছোট করা যাবে +- লাইব্রেরি সংস্করণ আপডেট এবং ডাটাবেস ব্যাকআপ নিষ্কাশন #1510 +- ExoPlayer 2.8.2 আপডেট #1392 + - দ্রুত গতি পরিবর্তনের জন্য বিভিন্ন ধাপের আকার সমর্থন করতে প্লেব্যাক গতি নিয়ন্ত্রণ ডায়ালগ পুনরায় কাজ করেছে৷ + - প্লেব্যাক গতি নিয়ন্ত্রণে নীরবতার সময় ফাস্ট-ফরওয়ার্ডে একটি টগল যুক্ত করা হয়েছে। এটি অডিওবুক এবং নির্দিষ্ট সঙ্গীত ঘরানার জন্য সহায়ক হওয়া উচিত এবং একটি সত্যিকারের বিরামহীন অভিজ্ঞতা আনতে পারে (এবং প্রচুর নীরবতার সাথে একটি গান ভাঙতে পারে =\\)। + - ম্যানুয়ালি না করে প্লেয়ারে অভ্যন্তরীণভাবে মিডিয়ার পাশাপাশি মেটাডেটা পাস করার অনুমতি দেওয়ার জন্য রিফ্যাক্টর করা মিডিয়া সোর্স রেজোলিউশন। এখন আমাদের কাছে মেটাডেটার একটি একক উৎস আছে এবং প্লেব্যাক শুরু হলে সরাসরি উপলব্ধ। + - প্লেলিস্ট ফ্র্যাগমেন্ট খোলার সময় নতুন মেটাডেটা উপলব্ধ হলে রিমোট প্লেলিস্ট মেটাডেটা আপডেট হচ্ছে না। + - বিভিন্ন UI ফিক্স: #1383, ব্যাকগ্রাউন্ড প্লেয়ার নোটিফিকেশন কন্ট্রোল এখন সবসময় সাদা, ফ্লিংিংয়ের মাধ্যমে পপআপ প্লেয়ার বন্ধ করা সহজ +- মাল্টি সার্ভিসের জন্য রিফ্যাক্টরযুক্ত আর্কিটেকচার সহ নতুন এক্সট্র্যাক্টর ব্যবহার করুন + +### সমাধান + +- #1440 ব্রোকেন ভিডিও ইনফো লেআউট #1491 ঠিক করুন +- দেখুন ইতিহাস সংশোধন #1497 + - #1495, ব্যবহারকারী প্লেলিস্ট অ্যাক্সেস করার সাথে সাথে মেটাডেটা (থাম্বনেল, শিরোনাম এবং ভিডিও গণনা) আপডেট করে। + - #1475, ডাটাবেসে একটি ভিউ রেজিস্টার করে যখন ব্যবহারকারী ডিটেইল ফ্র্যাগমেন্টে এক্সটার্নাল প্লেয়ারে একটি ভিডিও শুরু করে। +- পপআপ মোডের ক্ষেত্রে ক্রিনের সময়সীমা ঠিক করুন। #1463 (স্থির #640) +- প্রধান ভিডিও প্লেয়ার ফিক্স #1509 + - [#1412] ফিক্সড রিপিট মোড প্লেয়ার এনপিই সৃষ্টি করে যখন প্লেয়ার অ্যাক্টিভিটি ব্যাকগ্রাউন্ডে থাকে তখন নতুন ইন্টেন্ট পাওয়া যায়। + - পপআপের জন্য ফিক্সড মিনিমাইজিং প্লেয়ার প্লেয়ারকে ধ্বংস করে না যখন পপআপের অনুমতি না দেওয়া হয়। diff --git a/fastlane/metadata/android/bn/changelogs/730.txt b/fastlane/metadata/android/bn/changelogs/730.txt index fd2b8e1bb..4d0e49a84 100644 --- a/fastlane/metadata/android/bn/changelogs/730.txt +++ b/fastlane/metadata/android/bn/changelogs/730.txt @@ -1,2 +1,2 @@ -# ঠিক করা -- দ্রুত ঠিককরণ ডিক্রিপ্ট ফাংশন ত্রুটি আবারো। +# নিস্কাসিত +- দ্রুত হট নিষ্কাসন ডিক্রিপ্ট ফাংশন ত্রুটি আরেকবার। diff --git a/fastlane/metadata/android/bn/changelogs/770.txt b/fastlane/metadata/android/bn/changelogs/770.txt index 0c853ea74..5653c91ff 100644 --- a/fastlane/metadata/android/bn/changelogs/770.txt +++ b/fastlane/metadata/android/bn/changelogs/770.txt @@ -1,4 +1,4 @@ ০.১৭.২ এ পরিবর্তনসূচী -ঠিককরণ +নিষ্কাসন • কোনো ভিডিও নেই সমস্যা ঠিক করা হয়েছে diff --git a/fastlane/metadata/android/bn/changelogs/956.txt b/fastlane/metadata/android/bn/changelogs/956.txt index a01a4c8ed..48caa0b16 100644 --- a/fastlane/metadata/android/bn/changelogs/956.txt +++ b/fastlane/metadata/android/bn/changelogs/956.txt @@ -1 +1 @@ -[ইউটিউব] যখন কোন ভিডিও লোড হচ্ছিলো ক্র্যাশ ফিক্স করা হয়েছে +[ইউটিউব] কোনো ভিডিও লোড হওয়ার মধ্যের ক্র্যাশ নিষ্কাসন করা হয়েছে diff --git a/fastlane/metadata/android/bn/short_description.txt b/fastlane/metadata/android/bn/short_description.txt index 1085f6c73..6c5a1ea73 100644 --- a/fastlane/metadata/android/bn/short_description.txt +++ b/fastlane/metadata/android/bn/short_description.txt @@ -1 +1 @@ -অ্যানড্রয়েডের জন্য একটি মুক্ত ও হালকা ইউটিউব ফ্রন্টএন্ড। +অ্যানড্রয়েডের জন্য একটি মুক্ত ও সরল ইউটিউব ফ্রন্টএন্ড। diff --git a/fastlane/metadata/android/bn_BD/changelogs/63.txt b/fastlane/metadata/android/bn_BD/changelogs/63.txt new file mode 100644 index 000000000..efb6558d3 --- /dev/null +++ b/fastlane/metadata/android/bn_BD/changelogs/63.txt @@ -0,0 +1,8 @@ +### উন্নতিসমূহ +- Import/export সেটিংস #1333 +- চিত্রণ কমানো (দক্ষতার উন্নতি) #1371 +- ছোটখাটো কোডে উন্নতি #1375 +- GDPR এর সবকিছু যোগ হওয়া #1420 + +### নিষ্কাসীত +- ডাউনলোডার: .giga ফাইল থেকে অসমাপ্ত ডাউনলোড গুলোর থেমে যাওয়া লোড এর নিষ্কাসন #1407 diff --git a/fastlane/metadata/android/bn_BD/changelogs/64.txt b/fastlane/metadata/android/bn_BD/changelogs/64.txt new file mode 100644 index 000000000..502ceb579 --- /dev/null +++ b/fastlane/metadata/android/bn_BD/changelogs/64.txt @@ -0,0 +1,8 @@ +### উন্নতিসমুহ +- মোবাইল ডেটা ব্যাবহারে ভিডিও মানে সীমা দেয়ার ক্ষমতা যোগ করা হয়েছে। #1339 +- সেশন এর মাধ্যমে উজ্জ্বলতা মনে রাখা। #1442 +- দুর্বল সিপিইউগুলোতে ডাউনলোডের দক্ষতার উন্নতি। #1431 +- মিডিয়া সেশনগুলোই সাহায্যকারী যোগ করা হয়েছে। #1433 + +### নিষ্কাশন +- ডাউনলোডগুলো খুলতে বিধ্বস্ত হওয়া নিষ্কাসন( ছেড়ে রাখা নির্মাণ গুলোর জন্যেও নিষ্কাসন উপলুদ্ধ) #1441 diff --git a/fastlane/metadata/android/bn_BD/full_description.txt b/fastlane/metadata/android/bn_BD/full_description.txt new file mode 100644 index 000000000..ff00b080f --- /dev/null +++ b/fastlane/metadata/android/bn_BD/full_description.txt @@ -0,0 +1 @@ +নিউপাইপ গুগলের বা ইউটিউবের কোনো ফ্রেমওয়ার্ক লাইব্রেরি ব্যাবহার করেনা। এটা শুধু ওয়েবসাইট গুলোকে পারস করে যে তথ্যগুলো দরকার সেগুলোর প্রয়োজনে। এজন্যেই এই অ্যাপটা গুগলের কোনো সেবা ইনস্টল করা ছাড়াই ব্যাবহার করা যায়। আর, নিউপাইপ ব্যাবহার করতে তোমার কোনো ইউটিউব একাউন্ট প্রয়োজন হবে না, আর এইটা ফেশোর মতো। diff --git a/fastlane/metadata/android/bn_BD/short_description.txt b/fastlane/metadata/android/bn_BD/short_description.txt new file mode 100644 index 000000000..c14b3261b --- /dev/null +++ b/fastlane/metadata/android/bn_BD/short_description.txt @@ -0,0 +1 @@ +অ্যান্ড্রয়েড এর জন্যে একটা মুক্ত সরল ইউটিউব ফ্রন্টএন্ড। diff --git a/fastlane/metadata/android/cs/changelogs/63.txt b/fastlane/metadata/android/cs/changelogs/63.txt new file mode 100644 index 000000000..f98de65de --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/63.txt @@ -0,0 +1,8 @@ +### Improvements +- Import/export settings #1333 +- Reduce overdraw (performance improvement) #1371 +- Small code improvements #1375 +- Add everything about GDPR #1420 + +### Fixed +- Downloader: Fix crash on loading unfinished downloads from .giga files #1107 diff --git a/fastlane/metadata/android/cs/changelogs/64.txt b/fastlane/metadata/android/cs/changelogs/64.txt new file mode 100644 index 000000000..7b2bd1ce5 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/64.txt @@ -0,0 +1,8 @@ +### Vylepšení +- Přidáni možnosti omezení kvality videa při použití mobilních dat. #1339 +- Zapamatování jasu pro relaci. #1442 +- Zlepšený výkon pro stahování se slabším CPU #1431 +- Přidána (fungující) podpora pro mediální relace #1433 + +### Oprava +- Opraveno selhání aplikace při otevření stáhnutých souborů (oprava je nyní k dispozici pro vydané sestavy. #1441 diff --git a/fastlane/metadata/android/cs/changelogs/65.txt b/fastlane/metadata/android/cs/changelogs/65.txt new file mode 100644 index 000000000..8523ba313 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/65.txt @@ -0,0 +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. + - + - + - + - +- +- +- +- +- +- + - + - +- +- + - + -. diff --git a/fastlane/metadata/android/cs/changelogs/66.txt b/fastlane/metadata/android/cs/changelogs/66.txt new file mode 100644 index 000000000..d62f6db4f --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/66.txt @@ -0,0 +1,16 @@ +# Changelog of v0.13.7 + +### Fixed +- Fix sort filter issues of v0.13.6 + +# Changelog of v0.13.6 + +### 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 diff --git a/fastlane/metadata/android/cs/changelogs/68.txt b/fastlane/metadata/android/cs/changelogs/68.txt new file mode 100644 index 000000000..528100d34 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/68.txt @@ -0,0 +1,15 @@ +# changes of v0.14.1 + +### Fixed +- Fixed failed to decrypt video url #1659 +- Fixed description link not extract well #1657 + +# changes of v0.14.0 + +### New +- New Drawer design #1461 +- New customizable front page #1461 + +### Improvements +- Reworked Gesture controls #1604 +- New way to close the popup player #1597 diff --git a/fastlane/metadata/android/cs/changelogs/69.txt b/fastlane/metadata/android/cs/changelogs/69.txt new file mode 100644 index 000000000..48113281b --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/69.txt @@ -0,0 +1,9 @@ +### New +- Odstranění a sdílení v odběrech dlouhým klepnutím #1516 +- Uživatelské rozhraní tabletu a rozvržení seznamu mřížky #1617 + +### Vylepšení +- ukládání a opětovné načítání naposledy použitého poměru stran #1748 +- Povolení lineárního rozložení v aktivitě Stahování s úplnými názvy videí #1771 +- Odstraňování a sdílení odběrů přímo z karty odběrů #1516 +- Enqueuing nyní spustí přehrávání videa, pokud fronta přehrávání již skončila #1783 diff --git a/fastlane/metadata/android/cs/changelogs/70.txt b/fastlane/metadata/android/cs/changelogs/70.txt new file mode 100644 index 000000000..6a4f43976 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/70.txt @@ -0,0 +1,7 @@ +UPOZORNĚNÍ: Tato verze je pravděpodobně plná chyb, stejně jako ta předchozí. Nicméně vzhledem k úplnému vypnutí od 17. je lepší rozbitá verze než žádná. Nebo ne? ¯\_(ツ)_/¯ + +### Vylepšení +* stažené soubory lze nyní otevřít jedním kliknutím #1879 +* podpora upuštění pro Android 4.1 - 4.3 #1884 +* odstranění starého přehrávače #1884 +* odstranění streamů z aktuální fronty přehrávání přejetím doprava #1915 diff --git a/fastlane/metadata/android/cs/changelogs/71.txt b/fastlane/metadata/android/cs/changelogs/71.txt new file mode 100644 index 000000000..a0fdaa76f --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/71.txt @@ -0,0 +1,7 @@ +### Vylepšení +* Přidání oznámení o aktualizaci aplikace pro sestavení na GitHubu (#1608 by @krtkush) +* Různá vylepšení downloaderu (#1944 by @kapodamy): + * přidat chybějící bílé ikony a použít hardcorový způsob změny barev ikon + * kontrola, zda je iterátor inicializován (oprava #2031) + * umožnit opakování stahování při chybě "post-processing failed" v novém muxeru + * nový muxer MPEG-4 opravuje nesynchronní video a audio toky (#2039) diff --git a/fastlane/metadata/android/cs/changelogs/730.txt b/fastlane/metadata/android/cs/changelogs/730.txt new file mode 100644 index 000000000..13d0879c8 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/730.txt @@ -0,0 +1,2 @@ +# Fixed +- Znovu opravte chybu funkce dešifrování. diff --git a/fastlane/metadata/android/cs/changelogs/740.txt b/fastlane/metadata/android/cs/changelogs/740.txt new file mode 100644 index 000000000..479d25e25 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/740.txt @@ -0,0 +1,23 @@ +

Improvements

+