diff --git a/.gitignore b/.gitignore index c3d0e23ef..9f082390d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,15 @@ build.xml #eclipse project files .metadata .settings +#IntelliJ project files +.idea +*.iml +gen-external-apklibs +out +#transifex downloads +changelog +description + # other *.odg# proguard diff --git a/.tx/config b/.tx/config new file mode 100644 index 000000000..3a05b8725 --- /dev/null +++ b/.tx/config @@ -0,0 +1,34 @@ +[main] +host = https://www.transifex.com + +[antennapod.english] +source_file = res/values/strings.xml +source_lang = en +trans.az = res/values-az/strings.xml +trans.ca = res/values-ca/strings.xml +trans.cs_CZ = res/values-cs-rCZ/strings.xml +trans.da = res/values-da/strings.xml +trans.de = res/values-de/strings.xml +trans.es = res/values-es/strings.xml +trans.es_ES = res/values-es-rES/strings.xml +trans.fr = res/values-fr/strings.xml +trans.it_IT = res/values-it-rIT/strings.xml +trans.pt = res/values-pt/strings.xml +trans.pt_BR = res/values-pt-rBR/strings.xml +trans.ro_RO = res/values-ro-rRO/strings.xml +trans.ru = res/values-ru/strings.xml +trans.ru-RU = res/values-ru/strings.xml +trans.ru_RU = res/values-ru/strings.xml +trans.uk_UA = res/values-uk-rUA/strings.xml +trans.zh_CN = res/values-zh-rCN/strings.xml + +[antennapod.description] +file_filter = description/.txt +source_file = description/en.txt +source_lang = en + +[antennapod.changelog] +file_filter = changelog/.md +source_file = CHANGELOG.md +source_lang = en + diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 888aa026a..38441a520 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="31" + android:versionName="0.9.7.4" > @@ -23,6 +23,9 @@ + @@ -52,6 +55,37 @@ android:configChanges="keyboardHidden|orientation" android:label="@string/add_new_feed_label" android:windowSoftInputMode="adjustResize" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -187,76 +221,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - \ No newline at end of file + diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c2d376d1..8e9b28fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Change Log ========== +Version 0.9.7.4 +--------------- +* Episode cache size can now be set to unlimited +* Removing an episode in the queue via sliding can now be undone +* Added support for Links in MP3 chapters +* Added Czech(Czech Republic), Azerbaijani and Portuguese translations +* Several bugfixes and improvements + Version 0.9.7.3 --------------- * Bluetooth devices now display metadata during playback (requires AVRCP 1.3 or higher) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 887bea94d..2f51914fe 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -7,6 +7,7 @@ ortylp LatinSuD wseemann hzulla +andrewgaul Translations: @@ -20,4 +21,7 @@ Spanish: frandavid100, Fitoschido, dvd1985 Spanish (Spain): e2jk, dvd1985, frandavid100 Ukrainian (Ukraine): zhenya97, older French: lacouture, e2jk -Italian (Italy): m.chinni \ No newline at end of file +Italian (Italy): m.chinni +Czech(Czech Republic): elich +Azerbaijani: phoenixar +Portuguese: smarquespt \ No newline at end of file diff --git a/assets/about.html b/assets/about.html index e0c972682..b2188fc8b 100644 --- a/assets/about.html +++ b/assets/about.html @@ -40,7 +40,7 @@ @@ -51,6 +51,9 @@

Android-ViewPagerIndicator (Link)

by Jake Wharton, licensed under the Apache 2.0 license +

NineOldAndroids (Link)

+ by Jake Wharton, licensed under the Apache 2.0 license +

Apache Commons (Link)

by The Apache Software Foundation, licensed under the Apache 2.0 license

flattr4j (Link)

diff --git a/pom.xml b/pom.xml index ad67f0b55..79060e0f8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ de.danoeh antennapod apk - 0.9.7.3 + 0.9.7.4 AntennaPod @@ -70,6 +70,11 @@ 0.6.1-SNAPSHOT apklib + + com.nineoldandroids + library + 2.4.0 + @@ -87,7 +92,7 @@ com.jayway.maven.plugins.android.generation2 android-maven-plugin - 3.5.0 + 3.6.1 ${env.ANDROID_HOME} @@ -167,6 +172,10 @@ ${sign.storepass} ${sign.keypass} true + + -sigalgMD5withRSA + -digestalgSHA1 + @@ -190,10 +199,11 @@ false - true + false - true + false + proguard.cfg diff --git a/proguard.cfg b/proguard.cfg index 795bbbb9c..3e5ad54e4 100644 --- a/proguard.cfg +++ b/proguard.cfg @@ -8,6 +8,11 @@ -optimizations !code/simplification/arithmetic -keepattributes *Annotation* +#-libraryjars libs/android-support-v4.jar +#-libraryjars libs/commons-lang3-3.1.jar +#-libraryjars libs/flattr4j-core-2.4.jar +#-libraryjars libs/commons-io-2.4.jar + -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml new file mode 100644 index 000000000..7047a2df2 --- /dev/null +++ b/res/values-az/strings.xml @@ -0,0 +1,231 @@ + + + + AntennaPod + Kanallar + PODKASTLAR + EPIZODLAR + Yeni + Gözləmədə + Parametrlər + Yeni kanal əlavə et + Yükləmələr + Yükləməyi ləğv et + Yükləmə jurnalı + Oynatma tarixiçəsi + + Brauzerdə aç + URLı kopiyala + URLı paylaş + URL buferə köçürüldü + + Tarixiçəni sildir + + Oldu + Ləğv et + Müəlif + Dil + Üz: + Xəta + Xəta baş verdi: + Təzələ + Heç bir yaddaş cihazı tapılmadı. + Fəsillər + Təsvir + Ən yeni epizod:\u0020 + \u0020epizod + Nəşr edən:\u0020 + Müddət:\u0020 + Ölçü:\u0020 + Hazırlaşma + Yükləmə... + Şəkil:\u0020 + + Kanalın URLı + Kanalın URLını yaz: + + Hamısını oxunmuş kimi işarələ + Məlumatı göstər + Kanlı sil + Web-səhifəyi paylaş + Kanalı paylaş + Bütün kanallar və epizodlar silinəçək. + + Yüklə + Oynat + Pauza + İnternetən yayimla + Sil + Oxumuş kimi işarələ + Oxunmamış kimi işarələ + Növbəyə əlavə et + Növbədən sil + Web-səhifəsini aç + Flattrla + Hamsını növbəyə əlavə et + Hamısını yüklə + Epizodu burax + + Yükləmə uğurlu keçdi + Yükləmə uğursuzdur + Yükləmə gözlənir + Yükləmə gedir + Yaddaş cihazı tapılmadı + Yaddaş çatmır + Fayl xətası + HTTP protokolnun xətası + Naməlum xəta + Parserin xətası + Naməlum kanal növü + Əlaqə xətasi + Naməlum xost + Yükləmələrin hamısını ləğv et + Yükləmə ləğv olundu + Yükləmə başa çatdı + Yanlış URL + IO xətasi + Tələbin xətası + \u0020yükləmə galdı + Podkast məlumatların yüklənişi + %1$d yükləmə uğurludur, %2$d uğursuzdur + Naməlum başliğ + Kanal + Mediya fayl + Şəkil + Fayl yükləmə xətası:\u0020 + + Xəta! + Heç nə oynadılmır + Hazırlanır + Hazır + Axtarış + Server iştəmir + Naməlum xəta + Heç nə oynadılmır + 00:00:00 + Buferləşmə + Podkast oynadılır + Daha çox info üçün bura bas + + Jurnalı göstər + Pleyeri göstər + + Növbəyi sil + Növbələ düzənlə + Qaytar + Element silindi + + Flattra gir + Girmə prosesini başlamaq üçün düyməyi basın. Flattrın giriş səhifəsinə aparılacağsınız. + Gir + Baş ekrana dön + Giriş uğurludur! İndi tətbiqlədən Flattrla istifadə edə bilərsiniz. + Heç bir Flattr tokeni tapılmadı + Olsun ki sizin Flattr hesabınız AntennaPod\'a qoşulmadı. Yenə Flattra girin ya da podkastın səhifəsinə keçin. + Gir + Əməliyyat qadağan olundu + Bu əməliyyat üçün AntennaPod\'un icazəsi yoxdur. AntennaPod\'un keçid tokenin ləğv olunması bunun səbəbi ola bilər. Yenə Flattra girin ya da podkastın səhifəsinə keçin. + Keçid ləğv olundu + AntennaPod\'un keçid tokeni uğurlu ləğv olundu. + Flattrma uğurludur + Flattrləmə + + Siyahıda heç nə yoxdur + Hələ heç bir kanala yazilmadınız + + Başqa + Proqram haqqinda + Növbə + Qulaqliqı ayiranda oynatma dayanacağ + Oynatma başa çatanda növbədə irəlidəki epizodu oynat + Oynatma + Şəbəkə + Təzələmə intervali + Kanalın avtomatik təzələməsinin intervalını seç ya da keçir onu + Təkçə Wi-Fi vasitəsiilə yüklə + Fasiləsiz oynatma + Wi-Fi vasitəsiilə yükləmə + Qulaqliqı ayır + Mobil şəbəbkə vasitəsiilə təzələmə + Mobil şəbəbkə vasitəsiilə təzələməyə icazə vermək + Təzələmə + Flattr parametrləri + Flattra gir + Flattr\'la istifadə etmək üçün, öz Flattr hesabınıza girin + Bu proqramı flattrla + Flattr vasitəsiilə AntennaPodun inkişafını dəstək edin. Sağolun! + Keçidi ləğv ət + Flattr hesabına keçidi ləğv et + Təkçə epizodları göstər + Təkçə daxilində epizod olan elementləri göstər + İnterfeys + Görüşü seç + AntennaPod\'un görüşünü dəyişdir + Avtomatik yükləmə + Epizodların avtomatik yüklənişinin konfiqurasiyanı dəyiş + Wi-Fi filtr + Seçilən Wi-Fi səbəkələr vasitəsiilə avtomatik yükləməyi icazə ver + Epizod keşi + + Qara + Hədsiz + saat + saat + Əl ilə + + Kanalları və ya epizodları axtar + Təsvirlərdə tapıldı + Fəsillərdə tapıldı + Axtarış... + Heç nə tapılmadı + Axtarışın nəticələri + Axtarılan:\u0020 + Axtar + Başlığda tapıldı + + Ya da OPML faylı idxal edə bilərsiniz. OPML fayl vasitəsiilə siz öz podkastlarınızı başqa podcast menecerə köcürə bilərsiniz: + OPML faylın idxalı üçün onu aşağıdakı qovluqa yerləşdirin və idxal prosesini başlamaq üçün düyməyi basın. + İdxalı başla + OPML idxalı + XƏTA! + OPML faylın oxunması + OPML faylını oxuyanda xəta baş verdi: + İdxal qovliqu boşdur. + Hamısını seç + Seçimi ləğv et + İdxal üçün fayl seç + OPML ixraçı + İxrac... + İxracın xətası + OPML ixracı uğurlu keçdi + OPML fayl:\u0020 yazılıb + + Yuxu taymerini qoy + Yuxu taymerini keçir + Vaxtı yaz + Yuxu taymeri + Vaxt galdı:\u0020 + Yanlış yazi. Vaxt təkçə rəqmlərlə yazılır + + Kategoriya yüklənişi... + MiroGuide\'da bax + Ya da MiroGuide\'da bax: + MiroGuide + MiroGuide\'da axtar + Populyar + Ən reytinqli + Kanalı əlavə et + Kanal əlavə olundu + + Seçilən qovluq: + Qovluqu yarat + Məlumat qovluqunu seç + \"%1$s\" adlı qovluq yaradılsınmı? + Yeni qovluq yaradıldı + Bu qovluqa yazıla bilinmer + Qovluq artiq var + Qovluq yaradılmadı + Qovluq boş deyil + Seçilən qovluq boş deyil. Mediya yükləmələr və başka fayllar bu qovluqa yazılacaqlar. Necə olsa davam olsunmu? + Başlanğıc qovluqu seç + diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index eb15eea29..df2c3b719 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -32,6 +32,7 @@ L\'emmagatzemament extern no està disponible. Assegureu-vos que està muntat per què l\'aplicació funcioni correctament. Capítols Notes del programa + Episodi més recent: \u0020 \u0020episodis Publicat:\u0020 Durada:\u0020 @@ -50,10 +51,10 @@ Comparteix l\'enllaç del canal Confirmeu que, efectivament, voleu suprimir aquest canal i tots els episodis que us n\'heu baixat. - Baixades + Baixa Reprodueix Pausa - Flux + Reprodueix sense baixar Suprimeix Marca com a llegit Marca com a pendent @@ -111,6 +112,8 @@ Buida la cua Ordena la cua + Desfés + Ítem esborrat Inici de sessió a Flattr Premeu el botó per iniciar el procés d\'autenticació. Quan s\'obri la pantalla d\'inici de sessió de Flattr al vostre navegador, introduïu les vostres credencials i concediu a AntennaPod els permisos de compartir mitjançant Flattr. En finalitzar el procés, tornareu automàticament a aquesta pantalla. @@ -133,18 +136,18 @@ Altres Quant a Cua - Pausa la reproducció en desconnectar els auriculars + Pausa la reproducció en desconnectar els auriculars. Salta al següent element de la cua en acabar la reproducció Reproducció Xarxa Interval d\'actualització - Especifiqueu l\'interval en què els canals s\'actualitzen de forma automàtica, o deshabiliteu la funcionalitat + Especifiqueu l\'interval en què els canals s\'actualitzen de forma automàtica, o deshabiliteu la funcionalitat. Només baixa fitxers a través d\'una xarxa sense fils Reproducció continuada Baixa a través de xarxes sense fils Desconnexió d\'auriculars Actualitzacions sobre xarxes mòbils - Permet actualitzacions a través de xarxes mòbils + Permet actualitzacions a través de xarxes mòbils. S\'està actualitzant Configuració de Flattr Inici de sessió Flattr @@ -152,17 +155,23 @@ Compartiu aquesta aplicació amb Flattr Doneu suport al desenvolupament d\'AntennaPod compartint l\'aplicació a través de Flattr. Gràcies! Revoca l\'accés - Revoca el permís d\'accés d\'aquesta aplicació al vostre compte Flattr. + Revoqueu el permís d\'accés d\'aquesta aplicació al vostre compte Flattr. Mostra només episodis Mostra només els elements que tenen algun episodi. Interfície d\'usuari - Seleccioneu un tema + Selecció de tema Canvieu l\'aparença d\'AntennaPod. Baixada automàtica - Configura la baixada automàtica d\'episodis. + Configureu la baixada automàtica d\'episodis. Activa el filtre de la xarxa sense fils Permet les baixades automàtiques només per a les xarxes sense fils seleccionades. Memòria d\'episodis + Clar + Fosc + Sense límits + hores + hora + Manual Cerca canals o episodis Trobat a notes del programa @@ -175,7 +184,7 @@ Trobat al títol També podeu importar fitxers OPML. Els fitxers OPML us permeten moure els podcasts d\'una aplicació a una altra: - Per importar un fitxer OPML, l\'heu d\'ubicar al següent directori i prémer el botó de sota per iniciar el procés. + Per importar un fitxer OPML, ubiqueu-lo al següent directori i premeu el botó de sota per iniciar el procés. Inicia la importació Importació OPML Error! diff --git a/res/values-cs-rCZ/strings.xml b/res/values-cs-rCZ/strings.xml new file mode 100644 index 000000000..84db0c7f6 --- /dev/null +++ b/res/values-cs-rCZ/strings.xml @@ -0,0 +1,231 @@ + + + + AntennaPod + Zdroje + PODCASTY + EPIZODY + Nový + Seznam nepřečtených + Nastavení + Přidat nový zdroj + Stahování + Zrušit stahování + Historie stáhnování + Historie přehrávání + + Otevřít v prohlížeči + Kopírovat URL + Sdílet URL + URL zkopírováno do schránky. + + Vymazat historii + + Potvrdit + Zrušit + Autor + Jazyk + Obal + Chyba + Nastala chyba: + Obnovit + Není dostupné žádné externí uložiště. Pro správnou funkci aplikace se prosím ujistěte, že je připojeno externí úložiště. + Kapitoly + Poznámky + Poslední epizoda:\u0020 + \u0020epizod + Publikováno:\u0020 + Délka:\u0020 + Velikost:\u0020 + Zpracovávám + Načítám... + Obrázek:\u0020 + + URL zdroje + Zadejte URL zdroje: + + Označit vše jako přečtené + Informace o zdroji + Odstranit zdroj + Sdílet odkaz + Sdílet adresu zdroje + Prosím potvrďte, že chcete smazat tento zdroj včetně všech stažených epizod. + + Stáhnout + Přehrát + Pozastavit + Streamovat + Odstranit + Označit jako přečtené + Označit jako nepřečtené + Přidat do fronty + Odebrat z fronty + Navštívit stránku + Flattr + Vše do fronty + Stáhnout vše + Přeskočit epizodu + + Stahování dokončeno + Stahování selhalo + Čekající na stažení + Probíhající stahování + Úložné zařízení nenalezeno + Nedostatek volného místa + Souborová chyba + HTTP chyba + Neznámá chyba + Výjimka parseru + Nepodporovaný typ zdroje + Chyba spojení + Neznámý host + Zrušit všechna stahování + Stahování zrušeno + Všechna stahování dokončena + Chybné URL + IO chyba + Chyba požadavku + \u0020Stahování zbývá + Stahuji podcast data + %1$d úspěšných stahování, %2$d selhalo + Neznámý název + Zdroj + Soubor + Obrázek + Nastala chyba při pokusu o stažení souboru:\u0020 + + Chyba! + Žádné probíhající přehrávání + Připravuji + Připraven + Přetáčím + Server nereaguje + Neznámá chyba + Žádné probíhající přehrávání + 00:00:00 + Načítání + Přehrávaný podcast + Stiskni zde pro více informací + + Historie stahování + Přehrávač + + Vyprázdnit frontu + Přeuspořádat frontu + Zpět + Položka odebrána + + Flattr přihlášení + Stiskněte následující tlačítko pro spuštění autentizačního procesu. Budete přesměrováni na přihlašovací obrazovku flattru a vyzváni k potvrzení udělení práv pro použití flattru aplikací AntennaPod. Po udělení práv se automaticky vrátíte na tuto obrazovku. + Přihlásit + Návrat domů + Úspěšně přihlášen. Nyní můžete využít flattru přímo v aplikaci. + Nenalezen Flattr token + Váš flattr učet není napojen do AntenaPodu. Můžete buďto napojit váš flattr účet do AntennaPodu a využít flattru přímo v aplikaci a nebo použít flattr přímo na webových stránkách zdroje v prohlížeči. + Přihlásit + Akce zakázána + AntennaPod nemá oprávnění pro tuto akci. Důvodem může být revokování přístupového tokenu AntennaPodu k vašemu účtu. Přístup můžete obnovit nebo využít prohlížeče k návštěvě stránky zdroje. + Přístup revokován + Úspěšně revokován přístup AntennPodu k vašemu účtu. Pro dokončení tohoto procesu je ještě zapotřebí na stránkách flattru odebrat z vašeho účtu AntennaPod ze seznamu povolených aplikací. + Úspěšně flattrováno! + Flattruji + + Žádné položky v seznamu. + Zatím nebyly přidány žádné zdroje. + + Ostatní + O aplikaci + Fronta + Při odpojení sluchátek automaticky pozastavit přehrávání. + Po přehrání položky z fronty přejít automaticky na další. + Přehrávání + Síť + Interval aktualizace zdrojů + Udává interval, ve kterém se zdroje automaticky aktualizují nebo tuto funkci deaktivuje. + Stahovat soubory pouze pomocí WiFi + Kontinuální přehrávání + WiFi stahování + Odpojení sluchátek + Mobilní aktualizace + Povolit aktualizace pomocí mobilního připojení. + Obnovuji + Nastavení Flattr + Flattr přihlášení + Přihlásit se k flattr účtu a umožnit flattrování přímo z aplikace. + Flattrovat tuto aplikaci + Podpořit vývoj AntennaPodu na flatteru. Děkujeme! + Odebrat přístup + Odebere aplikaci přístupová práva k vašemu flattr účtu. + Zobrazit pouze epizody + Zobrazit pouze položky obsahující epizody. + Uživatelské rozhraní + Vybrat motiv + Změnit vzhled AntennaPod. + Automatické stahování + Nastavení automatického stahování epizod. + Zapnout Wi-Fi filtr + Povolit automatické stahování pouze pomocí vybraných Wi-Fi sítí. + Historie epizod + Světlý + Tmavý + Bez omezení + hodin + hodina + Ručně + + Hledat zdroje a epizody + Nalezeno v poznámkách k show + Nalezeno v kapitolách + Vyhledávám... + Žádné výsledky + Výsledky vyhledávání + Vyhledáváno:\u0020 + Vyhledat + Nalezeno v názvu + + Můžete také importovat OPML soubor. OPML soubory umožňují přenést vaše podcasty z jednoho podcast manažera do jiného: + Pro import OPML souboru je třeba ho nejdříve umístit do následujícího adresáře a poté pro zahájení procesu importu stisknout tlačítko. + Importovat + OPML import + CHYBA! + Načítání OPML souboru + Nastala chyba při čtení OPML souboru: + Adresář importu je prázdný. + Označit vše + Zrušit výběr + Vyberte soubor k importování + OPML export + Exportuji... + Chyba exportu + OPML export byl úspěšný. + OPML soubor byl zapsán do:\u0020 + + Nastavit časovač vypnutí + Deaktivovat časovač vypnutí + Zadejte čas + Časovač vypnutí + Zbývá času:\u0020 + Neplatný vstup, musí být zadáno celé číslo + + Načítám kategorie... + Procházet Miro Guide + Nebo procházet Miro Guide: + Miro Guide + Vyhledávat v Miro Guide + Populární + Nejlépe hodnocené + Přidat zdroj + Přidávám zdroj + + Vybraný adresář: + Vytvořit adresář + Vybrat umístění dat + Vytvořit adresář \"%1$s\"? + Nový adresář vytvořen + Nelze zapisovat do adresáře + Adresář již existuje + Nelze vytvořit adresář + Adresář není prázdný + Vybraný adresář není prázdný. Stažená media a ostatní soubory budou umístěny přímo do tohoto adresáře. Přesto pokračovat? + Vybrat hlavní adresář + diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 2e98a6e63..ae57da607 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -32,6 +32,7 @@ Ingen ekstern harddisk er tilgængelig. Vær venlig at sørge for at den eksterne hukommelse er monteret så app\'en kan fungere korrekt. Kapitler Afsnitsnoter + Seneste episoder:\u0020 \u0020episoder Offentliggjort:\u0020 Længde:\u0020 @@ -111,6 +112,8 @@ Fjern kø Arranger kø + Fortryd + Emne slettet Flattr log ind Tryk på knappen nedenfor for at starte godkendelsesprocessen. Du vil blive ført til flattr log ind siden i din browser og bedt om at give AntennaPod tilladelse til at flattr emner. Efter at du har givet tilladelsen vil du automatisk vende tilbage til denne side. @@ -163,6 +166,12 @@ Sæt Wi-Fi filter til Tillad kun automatisk download for de valgte Wi-Fi netværk Episode cache + Lys + Mørk + Uendelig + timer + time + Manuelt Søg efter feeds eller episoder Funder i showets noter diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index be723effe..3afd65052 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -112,6 +112,8 @@ Abspielliste leeren Abspielliste organisieren + Rückgängig + Element entfernt Flattr Anmeldung Drücke den Button unten um den Authentifizierungsprozess zu starten. Du wirst dann zur Flattr-Anmeldeseite weitergeleitet, wo du gefragt wirst, AntennaPod die Erlaubnis zu geben, Dinge zu flattrn. Nachdem du die Erlaubnis erteilt hast, kehrst du automatisch zu diesem Bildschirm zurück. @@ -164,6 +166,12 @@ W-LAN-Filter aktivieren Erlaube das automatische Herunterladen nur in ausgewählten W-LAN Netzwerken. Episodenspeicher + Hell + Dunkel + Unbegrenzt + Stunden + Stunde + Manuell Suche nach Feeds oder Episoden In Sendungsnotizen gefunden diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 09ad122f5..0afdd0d1e 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -32,8 +32,9 @@ No se encuentra un almacenamiento externo. Asegúrese de que su almacenamiento externo esté montado para que la aplicación funcione correctamente. Capítulos Notas del programa + Episodio más reciente:\u0020 \u0020episodios - Publicado:\u0020 + Publicado el:\u0020 Duración:\u0020 Tamaño:\u0020 Procesando @@ -41,7 +42,7 @@ Imagen de:\u0020 URL del canal - Escriba aquí la URL del canal + Escriba aquí la URL del canal: Marcar todo como leído Información del programa @@ -111,6 +112,8 @@ Limpiar la cola Organizar cola + Deshacer + Artículo eliminado Identificarse en Flattr Pulse el botón inferior para comenzar la autenticación. Su navegador abrirá la pantalla de identificación de Flattr y le preguntará si quiere conceder permiso a AntennaPod para valorar cosas. Tras concederlo, volverá a esta pantalla automáticamente. @@ -163,6 +166,12 @@ Activar el filtro WiFi Permitir la descarga automática sólo para las redes WiFi marcadas. Caché de episodios + Claro + Oscuro + Ilimitado + horas + hora + Manual Buscar canales o episodios Encontrado en las notas del programa diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 02fb68388..5589b59f3 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -32,6 +32,7 @@ Aucun stockage externe n\'est disponible. Merci de connecter un volume de stockage externe pour que l\'application puisse fonctionner correctement. Chapitres Notes d\'épisode + Episode le plus récent :\u0020 \u0020épisodes Publié :\u0020 Durée :\u0020 @@ -111,6 +112,8 @@ Effacer la liste Ordre de lecture + Annuler + Élément retiré Connecter à Flattr Appuyez sur le bouton ci-dessous pour vous authentifier. Vous serez envoyés à l\'écran de connexion Flattr dans le navigateur, et il vous sera demandé de donner à AntennaPod la permission de flattr. Une fois ceci fait, vous reviendrez automatiquement à cet écran. @@ -163,6 +166,12 @@ Activer le filtre Wi-Fi Autoriser le téléchargement automatique uniquement sur les réseaux Wi-Fi sélectionnés. Épisodes stockés localement + Clair + Sombre + Illimité + heures + heure + Manuel Chercher des flux ou épisodes Trouvé dans les notes diff --git a/res/values-it-rIT/strings.xml b/res/values-it-rIT/strings.xml index 96e0b0ca1..0fe45c2df 100644 --- a/res/values-it-rIT/strings.xml +++ b/res/values-it-rIT/strings.xml @@ -3,7 +3,7 @@ AntennaPod Feed - PODCASTS + PODCAST EPISODI Nuovo Lista d\'attesa @@ -32,6 +32,7 @@ Non risulta disponibile lo spazio di archiviazione esterno. Assicurati che lo spazio di archiviazione sia montato per permettere all\'applicazione di funzionare correttamente. Capitoli Note dell\'episodio + Episodi Recenti:\u0020 \u0020episodi Pubblicato:\u0020 Durata:\u0020 @@ -85,7 +86,7 @@ IO Error Request error \u0020Download rimasti - Download dati dei podcast in corso + Download dati podcast in corso %1$d download con successo, %2$d ko Titolo sconosciuto Feed @@ -111,6 +112,8 @@ Svuota la coda Riordina la coda + Undo + Oggetto rimosso Flattr sign-in Premi il tasto seguente per iniziare il processo di autenticazione. Sarai trasferito alla pagina di login di flattr sul tuo browser e ti sarà richiesto di garantire ad AntennaPod il permesso di effettuare microdonazioni. Dopo la tua autorizzazione, sarai riportato alla seguente schermata in modo automatico. @@ -137,13 +140,13 @@ Passa al prossimo episodio in coda quanto si completa una riproduzione Riproduzione Rete - Intervallo di aggiornamento + Intervallo di update Specifica un intervallo per l\'aggiornamento automatico dei feed o disabilitalo Abilita il download dei media solo tramite WiFi Playback continuo Download dei media su WiFi Disconnessione cuffie - Aggiornamenti su rete mobile + Update su rete mobile Permetti gli aggiornamenti tramite connessione dati mobile Aggiornamento Impostazioni Flattr @@ -163,6 +166,12 @@ Abilita il filtro Wi-Fi Abilita il download automatico solo per alcune reti Wi-Fi selezionate. Cache degli episodi + Light + Dark + Illimitato + ore + ora + Manuale Ricerca per Feed o Episodi Trovato nelle note dell\'episodio @@ -191,9 +200,9 @@ Esportazione OPML avvenuta con successo. Il file .opml è stato scritto su:\u0020 - Imposta il timer di spegnimento + Imposta timer Disabilita il timer di spegnimento - Inserisci il tempo + Tempo di spegnimento Timer di spegnimento Tempo residuo:\u0020 Input non valido, il campo deve essere un numero intero. diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml new file mode 100644 index 000000000..8b9383979 --- /dev/null +++ b/res/values-pt/strings.xml @@ -0,0 +1,231 @@ + + + + AntennaPod + Fontes + Podcasts + Episódios + Novo + Lista de espera + Definições + Adicionar fonte + Transferências + Cancelar transferência + Registo de transferências + Histórico de reprodução + + Abrir no navegador + Copiar URL + Partilhar URL + URL copiado para a área de transferência. + + Limpar histórico + + Confirmar + Cancelar + Autor + Idioma + Imagem + Erro + Ocorreu um erro: + Atualizar + Não existe um cartão SD. Certifique-se que inseriu o cartão corretamente. + Capítulos + Notas + Episódio mais recente:\u0020 + \u0020episódios + Publicado:\u0020 + Duração:\u0020 + Tamanho:\u0020 + A processar... + A carregar... + Imagem de:\u0020 + + URL da fonte + Introduza o URL da fonte: + + Marcar tudo como lido + Mostrar informações + Remover fonte + Partilhar ligação do sítio web + Partilhar ligação da fonte + Confirme a eliminação desta fonte e de todos os episódios a ela petencentes. + + Transferir + Reproduzir + Pausa + Emitir + Remover + Marcar como lido + Marcar como novo + Adicionar à fila + Remover da fila + Aceder ao sítio web + Flattr + Colocar tudo na fila + Transferir tudo + Ignorar episódio + + Transferência terminada + Erro ao transferir + Transferência pendente + Transferência atual + Cartão SD não encontrado + Espaço insuficiente + Erro no ficheiro + Erro HTTP + Erro desconhecido + Exceção do processador + Fonte não suportada + Erro de ligação + Servidor desconhecido + Cancelar transferências + Transferência cancelada + Transferências terminadas + URL inválido + Erro I/O + Erro de pedido + \u0020Transferências em falta + A transferir dados... + %1$d transferências efetuadas, %2$d falhadas + Título desconhecido + Fonte + Ficheiro multimédia + Imagem + Ocorreu um erro ao transferir o ficheiro:\u0020 + + Erro! + Nada em reprodução + A preparar + Pronto + A procurar + Erro de servidor + Erro desconhecido + Nada em reprodução + 00:00:00 + A processar... + Reproduzir podcast + Clique para mais informações + + Mostrar registo + Mostrar reprodutor + + Limpar fila + Organizar fila + Anular + Item removido + + Sessão Flattr + Prima o botão abaixo para iniciar a autenticação. O seu navegador web abrirá o ecrã da sessão flattr e ser-lhe-á solicitada a permissão para o AntennaPod efetuar as alterações. Após ser dada a permissão, voltará novamente a este ecrã. + Autenticar + Voltar ao ecrã + Autenticação efetuada! Já pode fazer o flattr com a aplicação. + Token flattr não encontrado + Parece que a sua conta flattr não está vinculada ao AntennaPod. Pode vincular a sua conta ao AntennaPod ou aceder ao sítio web para fazer o flattr. + Autenticar + Ação negada + O AntennaPod não possui as permissões para esta ação. É possível que o token de acesso ao flattr via AntennaPod tenha sido revogado. Pode efetuar nova autenticação ou aceder ao sítio web do item. + Acesso revogado + Você revogou o token de acesso do AntennaPod à sua conta. Para concluir o processo, tem que remover esta aplicação da lista de aplicações presentes nas definições de conta no sítio web do flattr. + Flattered com sucesso! + Flattring + + Não existem itens na lista. + Ainda não possui quaisquer fontes. + + Outras + Sobre + Fila + Parar reprodução ao remover os auscultadores + Ir para a faixa seguinte ao terminar a reprodução + Reprodução + Rede + Intervalo entre atualizações + Indique o intervalo de tempo entre as atualizações de fontes ou desative a opção + Apenas transferir pelas redes sem fios + Reprodução contínua + Transferência Wi-Fi + Auscultadores removidos + Atualizações móveis + Permitir atualizações através da rede de dados + A atualizar + Definições flattr + Sessão flattr + Inicie sessão na sua conta flattr para fazer o flattr através do AntennaPod. + Flattr desta aplicação + Ajude no desenvolvimento do AntennaPod através do Flattr. Obrigado! + Revogar acesso + Revogar permissões de acesso da aplicação à sua conta flattr. + Mostrar apenas episódios + Apenas mostrar itens que possuam episódios. + Interface + Escolha o tema + Mudar o aspeto do AntennaPod. + Transferência automática + Configure a transferência automática dos episódios. + Ativar filtro Wi-Fi + Apenas permitir transferências automáticas através de redes sem fios. + Cache de episódios + Claro + Escuro + Sem limite + horas + hora + Manual + + Procurar fontes ou episódios + Encontrado nas notas + Encontrado nos capítulos + A procurar... + Nenhum resultado + Resultados da procura + Você procurou:\u0020 + Procura + Encontrado no título + + Também pode importar ficheiros OPML. Estes ficheiros permitem-lhe mover os seus podcasts entre aplicações. + Para importar um ficheiro OPML, tem que o colocar neste diretório e premir o botão abaixo para iniciar o processo. + Iniciar importação + Importação OPML + Erro! + A ler OPML + Ocorreu um erro ao ler o ficheiro OPML: + O diretório de importação está vazio. + Marcar tudo + Desmarcar tudo + Escolha o ficheiro a importar + Exportação OPML + A exportar... + Erro de exportação + Exportação efetuada. + O ficheiro .opml foi gravado em:\u0020 + + Definir temporizador + Desativar temporizador + Introduza o tempo + Temporizador + Tempo restante:\u0020 + Valor inválido. Tem que ser um inteiro. + + A carregar categorias... + Explorar o guia Miro + Ou explore o guia Miro: + Guia Miro + Procurar no guia Miro + Popular + Melhor avaliados + Adicionar fonte + A fonte está a ser adicionada + + Diretório escolhido: + Criar diretório + Escolha o diretório + Criar um diretório com o nome \"%1$s\"? + Novo diretório criado + Não é possível gravar neste diretório + O diretório já existe + Não é possível criar o diretório + Diretório não vazio + O diretório escolhido não está vazio. As transferências serão colocadas neste diretório. Continuar? + Escolha a pasta pré-definida + diff --git a/res/values-ro-rRO/strings.xml b/res/values-ro-rRO/strings.xml index d0bc9fdec..c1320964a 100644 --- a/res/values-ro-rRO/strings.xml +++ b/res/values-ro-rRO/strings.xml @@ -32,6 +32,7 @@ Nu exista stocare externă. Asigurați-vă că stocarea externă este conectată pentru ca aplicația să funcționeze corespunzător. Capitole Notițe + Cel mai recent episod:\u0020 \u0020episoade Publicat:\u0020 Durată:\u0020 @@ -111,6 +112,8 @@ Golește coada Organizează coada + Refă + Element înlăturat Flattr sign-in Apăsați butonul de mai jos pentru a începe procesul de autentificare. Veți fi îndreptat spre pagina de logare flattr în browser și veți fi rugat să acordați permisiuni AntennaPod sa flattr. După ce veți acorda permisiunile veți fi readuși la acest ecran automat. @@ -163,6 +166,12 @@ Pornește filtru Wi-Fi Pornește descărcarea automată doar pentru rețele Wi-Fi selectate. Cache de episoade + Deschis + Întunecat + Nelimitat + ore + oră + Manual Caută feeduri sau episoade Găsit în notițe diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index eac5da4b1..871803e2d 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -9,9 +9,9 @@ В ожидании Настройки Добавить канал - Закачки - Отменить закачку - Журнал закачек + Загрузки + Отменить загрузку + Журнал загрузок История воспроизведения Открыть в браузере @@ -22,16 +22,17 @@ Очистить историю Подтвердить - Отменить + Отмена Автор Язык Обложка Ошибка Произошла ошибка: Обновить - Внешний носитель данных недоступен. Убедитесь что внешний носитель смонтирован, иначе это приложение не сможет нормально работать. + Внешний носитель недоступен. Убедитесь что внешний носитель смонтирован, иначе приложение не сможет нормально работать. Разделы Описание + Следующий эпизод:\u0020 \u0020 выпуск(ов) Опубликовано:\u0020 Продолжительность:\u0020 @@ -50,25 +51,25 @@ Поделиться ссылкой на канал Подтвердите удаление канала и ВСЕХ закачанных выпусков этого канала. - Закачать - Слушать + Загрузить + Воспроизвести Пауза - Воспроизвести из сети + Потоковое воспроизведение Удалить Отметить как прочитанное Отметить как непрочитанное - Поставить в очередь + Добавить в очередь Удалить из очереди Посетить сайт Поддержать посредством Flattr - Поставить в очередь все - Скачать все + Добавить все в очередь + Загрузить все Пропустить выпуск - Закачка завершилась успешно - Закачка - Закачка в ожидании - Закачка в процессе + Загрузка завершена + Загрузка прервана + Загрузка в ожидании + Загрузка в процессе Устройство хранения не найдено Недостаточно свободного места Ошибка файла @@ -78,39 +79,41 @@ Неподдерживаемый тип канала Ошибка соединения Неизвестный хост - Отменить все закачки - Закачка отменена - Закачки завершены + Отменить все загрузки + Загрузка отменена + Загрузки завершены Некорректная ссылка Ошибка ввода-вывода Ошибка запроса - \u0020закачек осталось + Осталось\u0020загрузок Получение данных подкаста - %1$d успешных закачек, %2$d неудачных + %1$d загрузок завершено, %2$d не удалось Неизвестное название Канал Медиа файл Изображение - Ошибка скачивания файла:\u0020 + Ошибка при загрузки файла:\u0020 Ошибка! Ничего не воспроизводится Подготовка Готово - Поиск - Сервер отключился + Перемотка + Сервер отключен Неизвестная ошибка Ничего не воспроизводится 00:00:00 Буферизация Воспроизведение подкаста - Для получения дополнительной информации нажмите здесь + Нажмите для получения дополнительной информации Показать журнал Показать проигрыватель Очистить очередь Упорядочить очередь + Отмена + Удален Авторизоваться в Flattr Нажмите кнопку чтобы начать процесс авторизации. Вы будете перенаправлены на сайт Flattr где вам нужно будет подключить AntennaPod к вашему аккаунту. После этого вы автоматически будете перенаправлены обратно. @@ -133,15 +136,15 @@ Прочее О программе Очередь - Остановить воспроизведение когда наушники отсоединены + Остановить воспроизведение, когда наушники отсоединены После завершения воспроизведения перейти к следующему в очереди Воспроизведение Сеть Интервал обновлений - Укажите интервал через который каналы обновляются автоматически, или отключите это - Закачивать файлы только по WiFi + Укажите интервал через который каналы обновляются автоматически, или отключите его + Загружать файлы только по Wi-Fi Непрерывное воспроизведение - Закачка по WiFi + Загрузка по Wi-Fi Наушники отсоединены Мобильные обновления Обновлять каналы через мобильное интернет-подключение @@ -154,15 +157,21 @@ Отозвать доступ Отменить доступ этого приложения к вашему аккаунту Flattr. Показывать только выпуски - Показывать только те элементы списка которые содержат выпуски + Показывать только те элементы списка, которые содержат выпуски Интерфейс Выбор темы - Изменить оформление AntennaPod + Изменить тему оформление AntennaPod Автоматическая загрузка Настроить автоматическую загрузку выпусков. Включить фильтр Wi-Fi - Разрешать автоматическую загрузку тольуо для выбранных Wi-Fi сетей. + Разрешать автоматическую загрузку только для выбранных Wi-Fi сетей. Кэш выпусков + Светлая + Тёмная + Неограничен + Часы + Час + Вручную Поиск каналов или выпусков Найдено в описании выпуска @@ -174,37 +183,37 @@ Поиск Найдено в заголовке - Также возможен импорт файла OPML. OPML файлы позволяют переносить ваши подписки с подкастами из одного приложения в другое. + Также возможен импорт файла OPML. OPML файлы позволяют переносить ваши подписки из одного приложения в другое: Для импорта файла OPML его нужно поместить каталог указанный ниже и нажать кнопку чтобы начать процесс импорта. Начать импорт Импорт OPML ОШИБКА! Чтение файла OPML - Ошибка чтения документа OPML + Ошибка чтения файла OPML Каталог импорта пуст. Отметить все Снять все отметки Выбрать файл для импорта - Выгрузка в OPML - Выгружается... - Ошибка выгрузки - Выгружено в OPML успешно + Экспорт в OPML + Экспортируется... + Ошибка экспорта + Экспорт OPML завершен OPML файл был записан в:\u0020 Установить таймер сна Отключить таймер сна - Ввести время + Введите время Таймер сна Времени осталось:\u0020 Неправильный ввод, время должно быть в виде числа - Загружаются категории... - Просмотреть каталоги в проекте Miro - Или просмотреть каталоги в проекте Miro - Проект Miro - Поиск в проекте Miro + Загрузка категорий... + Посмотреть в каталоге Miro + Или посмотреть в каталоге Miro + Miro + Поиск в Miro Популярные - Наивысший рейтинг + С высоким рейтингом Добавить канал Канал добавляется @@ -217,6 +226,6 @@ Папка уже существует Невозможно создать папку Папка не пуста - Выбранная папка не пуста. Закачки и прочие файлы будут сохранены в эту папку. Продолжить? + Выбранная папка не пуста. Загрузки и прочие файлы будут сохранены в эту папку. Продолжить? Выберите папку по умолчанию diff --git a/res/values-uk-rUA/strings.xml b/res/values-uk-rUA/strings.xml index b2b6e7786..84f51ab7d 100644 --- a/res/values-uk-rUA/strings.xml +++ b/res/values-uk-rUA/strings.xml @@ -11,7 +11,7 @@ Додати канал Завантаження Скасувати завантаження - Завантаження + Історія завантажень Що грало Відкрити в браузері @@ -32,6 +32,7 @@ Немає доступної флешки. Зовнішній носій потрібен для коректної роботи додатку Глави Нотатки до епізода + Найновіший епізод:\u0020 \u0020епізодів Опубліковано:\u0020 Довжина:\u0020 @@ -53,11 +54,11 @@ Завантажити Грати Пауза - Поток + Прослухати без завантаження Видалити Прочитано Непрочитано - Додати канал + Додати до черги Видалити з черги Відкрити сайт Підтримати за допомогою Flattr @@ -111,14 +112,16 @@ Очистити чергу Впорядкувати чергу + Скасувати + Видалено - Увійти дл Flattr + Увійти до Flattr Нажміть цю кнопку для початку авторізації. Буде відкрито flattr в браузері, буде запит на дозвіл доступу Antennapod до flattr. Після надання доступу ви повернетесь до цього екрану автоматично Ввісти ім\'я та пароль Повернення до початку Вийшло авторізуватись. Тепер ви можете flattr things за допомогою додатку - Не має flattr token - Здається ваш обліковий запис flattr не підєднано до AntennaPod. Ви можете або підєднати її або відкривати web сторінку в браузері + Немає flattr token + Здається ваш обліковий запис flattr не під\'єднано до AntennaPod. Ви можете або під\'єднати її або відкривати web сторінку в браузері Пароль та логін Заборонено AntennaPod не маэ дозвілу це зробити. Можливо відкликаний доступ до AntennaPod. Або ввідіть логін пароль в налаштуваннях або зробить це на сайті @@ -157,12 +160,18 @@ Відображати тільки канали з наявними епізодами Зовнішній вид Обрати тему - Змінти появу AntennaPod + Змінити появу AntennaPod Автоматичне завантаження Налаштування автоматичного завантаження епізодів Увімкнути фільтр Wi-Fi - Дозволити автоматичне завантаження тільки для окремих Wi-Fi мережах + Дозволити автоматичне завантаження тільки в цих Wi-Fi мережах Кеш епізодів + Світла + Темна + Без обмежень + годин + година + Інструкція Пошук каналів та епізодів Знайдено у примітках @@ -174,7 +183,7 @@ Пошук Знайдено у назві - Ви можете імпортувати OPML файл. Такі файли дозволяют переходити з однієї програми до іншої: + Ви можете імпортувати OPML файл. Такі файли дозволяют переходити з однієї програми для подкастів до іншої: Для імпорту OPML файлу, скопіюйте його в цю папку та натіснить кнопку внизу для початку імпорту Почати імпорт OPML імпорт @@ -202,7 +211,7 @@ Перегляд Miro Guide Або проглянути Miro Guide Miro Guide - Пошук Mirog Guide + Пошук в Mirog Guide Популярні Кращі Додати канал diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 5e2840536..f7bf7d493 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -32,6 +32,7 @@ 没有可用的外部存储. 请确保安装外部存储器, 这样本应用才可以正常工作. 章节 笔记 + 最近曲目:\u0020 \u0020 曲 发表:\u0020 长度:\u0020 @@ -48,20 +49,20 @@ 删除订阅 分享网站链接 分享订阅链接 - 确认要删除这些订阅吗? 该订阅所有已经下载的插曲将一并删除. + 确认要删除这些订阅吗? 该订阅所有已经下载的曲目将一并删除. 下载 播放 暂停 - + 流媒体 删除 标记已读 标记未读 - 添加到队列 - 从队列中删除 + 添加到播放列表 + 从播放列表中删除 访问网站 Flattr 他 - 全部添加到队列 + 全部添加到播放列表 全部下载 跳过曲目 @@ -109,8 +110,10 @@ 下载日志 播放器 - 清空队列 - 组织队列 + 清空播放列表 + 组织播放列表 + 撤消 + 已删除项 Flattr 登录 按下面的按钮开始身份验证过程. 将在浏览器中打开 Flattr 登录界面并要求给予 AntennaPod 访问 Flattr 的权限. 权限许可后, 将自动回到这个界面. @@ -132,9 +135,9 @@ 其他 关于 - 队列 + 清空播放 当耳机是断开时暂停播放 - 播放完成跳转到队列下一项 + 播放完成跳转到播放列表下一项 播放 网络 更新周期 @@ -163,8 +166,14 @@ 打开 Wi-Fi 过滤器 只允许在 Wi-Fi 网络下自动下载 曲目缓存 + 浅色 + 暗色 + 无限 + 小时 + + 手动 - 搜索订阅或者插曲 + 搜索订阅或者曲目 笔记中查找 章节中查找 搜索中... @@ -199,11 +208,11 @@ 无效的输入, 时间是一个整数 加载目录中... - 浏览 Miro 指南 - 或者浏览 Miro 指南 - Miro 指南 - 搜索 Miro 指南 - 受欢迎的 + 浏览 Miro 频道 + 或者浏览 Miro 频道 + Miro 频道 + 搜索 Miro 频道 + 流行 评分最高 添加订阅 订阅已被添加 diff --git a/res/values/arrays.xml b/res/values/arrays.xml index b8cef4ab2..4036ff0f4 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1,16 +1,6 @@ - - Manual - 1 hour - 2 hours - 4 hours - 8 hours - 12 hours - 24 hours - - 0 1 @@ -20,7 +10,6 @@ 12 24 - @string/pref_episode_cache_unlimited 10 @@ -30,7 +19,6 @@ 80 100 - -1 10 @@ -40,20 +28,16 @@ 80 100 - N/A - 0 - @string/pref_theme_title_light @string/pref_theme_title_dark - 0 1 diff --git a/res/values/strings.xml b/res/values/strings.xml index 54438b9cc..b3f9975cb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,5 +1,8 @@ - + AntennaPod @@ -182,6 +185,10 @@ Light Dark Unlimited + hours + hour + Manual + Search for Feeds or Episodes @@ -244,4 +251,4 @@ The folder you have selected is not empty. Media downloads and other files will be placed directly in this folder. Continue anyway? Choose default folder - + \ No newline at end of file diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index b0968b79a..e94d1c47e 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -22,7 +22,7 @@ = 14) { - audioManager - .registerRemoteControlClient(remoteControlClient); - } - audioManager - .registerMediaButtonEventReceiver(mediaButtonReceiver); - media.onPlaybackStart(); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Failed to request Audiofocus"); - } - } - } + player.start(); + if (status != PlayerStatus.PAUSED) { + player.seekTo((int) media.getPosition()); + } + setStatus(PlayerStatus.PLAYING); + setupPositionSaver(); + setupWidgetUpdater(); + setupNotification(); + pausedBecauseOfTransientAudiofocusLoss = false; + if (android.os.Build.VERSION.SDK_INT >= 14) { + audioManager + .registerRemoteControlClient(remoteControlClient); + } + audioManager + .registerMediaButtonEventReceiver(mediaButtonReceiver); + media.onPlaybackStart(); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "Failed to request Audiofocus"); + } + } + } - private void writePlaybackPreferences() { - if (AppConfig.DEBUG) - Log.d(TAG, "Writing playback preferences"); + private void writePlaybackPreferences() { + if (AppConfig.DEBUG) + Log.d(TAG, "Writing playback preferences"); - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - if (media != null) { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - media.getPlayableType()); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, - shouldStream); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, - playingVideo); - if (media instanceof FeedMedia) { - FeedMedia fMedia = (FeedMedia) media; - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - fMedia.getItem().getFeed().getId()); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - fMedia.getId()); - } else { - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - media.writeToPreferences(editor); - } else { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } + SharedPreferences.Editor editor = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()).edit(); + if (media != null) { + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, + media.getPlayableType()); + editor.putBoolean( + PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, + shouldStream); + editor.putBoolean( + PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, + playingVideo); + if (media instanceof FeedMedia) { + FeedMedia fMedia = (FeedMedia) media; + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + fMedia.getItem().getFeed().getId()); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + fMedia.getId()); + } else { + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + } + media.writeToPreferences(editor); + } else { + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + } - editor.commit(); - } + editor.commit(); + } - private void setStatus(PlayerStatus newStatus) { - if (AppConfig.DEBUG) - Log.d(TAG, "Setting status to " + newStatus); - status = newStatus; - sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); - updateWidget(); - refreshRemoteControlClientState(); - bluetoothNotifyChange(); - } + private void setStatus(PlayerStatus newStatus) { + if (AppConfig.DEBUG) + Log.d(TAG, "Setting status to " + newStatus); + status = newStatus; + sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); + updateWidget(); + refreshRemoteControlClientState(); + bluetoothNotifyChange(); + } - /** - * Send ACTION_PLAYER_STATUS_CHANGED without changing the status attribute. - */ - private void postStatusUpdateIntent() { - setStatus(status); - } + /** Send ACTION_PLAYER_STATUS_CHANGED without changing the status attribute. */ + private void postStatusUpdateIntent() { + setStatus(status); + } - private void sendNotificationBroadcast(int type, int code) { - Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION); - intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); - intent.putExtra(EXTRA_NOTIFICATION_CODE, code); - sendBroadcast(intent); - } + private void sendNotificationBroadcast(int type, int code) { + Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION); + intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); + intent.putExtra(EXTRA_NOTIFICATION_CODE, code); + sendBroadcast(intent); + } - /** - * Used by setupNotification to load notification data in another thread. - */ - private AsyncTask notificationSetupTask; + /** Used by setupNotification to load notification data in another thread. */ + private AsyncTask notificationSetupTask; - /** - * Prepares notification and starts the service in the foreground. - */ - @SuppressLint("NewApi") - private void setupNotification() { - final PendingIntent pIntent = PendingIntent.getActivity(this, 0, - PlaybackService.getPlayerActivityIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT); + /** Prepares notification and starts the service in the foreground. */ + @SuppressLint("NewApi") + private void setupNotification() { + final PendingIntent pIntent = PendingIntent.getActivity(this, 0, + PlaybackService.getPlayerActivityIntent(this), + PendingIntent.FLAG_UPDATE_CURRENT); - if (notificationSetupTask != null) { - notificationSetupTask.cancel(true); - } - notificationSetupTask = new AsyncTask() { - Bitmap icon = null; + if (notificationSetupTask != null) { + notificationSetupTask.cancel(true); + } + notificationSetupTask = new AsyncTask() { + Bitmap icon = null; - @Override - protected Void doInBackground(Void... params) { - if (AppConfig.DEBUG) - Log.d(TAG, "Starting background work"); - if (android.os.Build.VERSION.SDK_INT >= 11) { - if (media != null && media != null) { - int iconSize = getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - icon = BitmapDecoder - .decodeBitmapFromWorkerTaskResource(iconSize, - media); - } + @Override + protected Void doInBackground(Void... params) { + if (AppConfig.DEBUG) + Log.d(TAG, "Starting background work"); + if (android.os.Build.VERSION.SDK_INT >= 11) { + if (media != null && media != null) { + int iconSize = getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width); + icon = BitmapDecoder + .decodeBitmapFromWorkerTaskResource(iconSize, + media); + } - } - if (icon == null) { - icon = BitmapFactory.decodeResource(getResources(), - R.drawable.ic_stat_antenna); - } + } + if (icon == null) { + icon = BitmapFactory.decodeResource(getResources(), + R.drawable.ic_stat_antenna); + } - return null; - } + return null; + } - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - if (!isCancelled() && status == PlayerStatus.PLAYING - && media != null) { - String contentText = media.getFeedTitle(); - String contentTitle = media.getEpisodeTitle(); - Notification notification = null; - if (android.os.Build.VERSION.SDK_INT >= 16) { - Intent pauseButtonIntent = new Intent( - PlaybackService.this, PlaybackService.class); - pauseButtonIntent.putExtra( - MediaButtonReceiver.EXTRA_KEYCODE, - KeyEvent.KEYCODE_MEDIA_PAUSE); - PendingIntent pauseButtonPendingIntent = PendingIntent - .getService(PlaybackService.this, 0, - pauseButtonIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - Notification.Builder notificationBuilder = new Notification.Builder( - PlaybackService.this) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setOngoing(true) - .setContentIntent(pIntent) - .setLargeIcon(icon) - .setSmallIcon(R.drawable.ic_stat_antenna) - .addAction(android.R.drawable.ic_media_pause, - getString(R.string.pause_label), - pauseButtonPendingIntent); - notification = notificationBuilder.build(); - } else { - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder( - PlaybackService.this) - .setContentTitle(contentTitle) - .setContentText(contentText).setOngoing(true) - .setContentIntent(pIntent).setLargeIcon(icon) - .setSmallIcon(R.drawable.ic_stat_antenna); - notification = notificationBuilder.getNotification(); - } - startForeground(NOTIFICATION_ID, notification); - if (AppConfig.DEBUG) - Log.d(TAG, "Notification set up"); - } - } + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + if (!isCancelled() && status == PlayerStatus.PLAYING + && media != null) { + String contentText = media.getFeedTitle(); + String contentTitle = media.getEpisodeTitle(); + Notification notification = null; + if (android.os.Build.VERSION.SDK_INT >= 16) { + Intent pauseButtonIntent = new Intent( + PlaybackService.this, PlaybackService.class); + pauseButtonIntent.putExtra( + MediaButtonReceiver.EXTRA_KEYCODE, + KeyEvent.KEYCODE_MEDIA_PAUSE); + PendingIntent pauseButtonPendingIntent = PendingIntent + .getService(PlaybackService.this, 0, + pauseButtonIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + Notification.Builder notificationBuilder = new Notification.Builder( + PlaybackService.this) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setOngoing(true) + .setContentIntent(pIntent) + .setLargeIcon(icon) + .setSmallIcon(R.drawable.ic_stat_antenna) + .addAction(android.R.drawable.ic_media_pause, + getString(R.string.pause_label), + pauseButtonPendingIntent); + notification = notificationBuilder.build(); + } else { + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder( + PlaybackService.this) + .setContentTitle(contentTitle) + .setContentText(contentText).setOngoing(true) + .setContentIntent(pIntent).setLargeIcon(icon) + .setSmallIcon(R.drawable.ic_stat_antenna); + notification = notificationBuilder.getNotification(); + } + startForeground(NOTIFICATION_ID, notification); + if (AppConfig.DEBUG) + Log.d(TAG, "Notification set up"); + } + } - }; - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - notificationSetupTask - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - notificationSetupTask.execute(); - } + }; + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + notificationSetupTask + .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + notificationSetupTask.execute(); + } - } + } - /** - * Seek a specific position from the current position - * - * @param delta offset from current position (positive or negative) - */ - public void seekDelta(int delta) { - int position = getCurrentPositionSafe(); - if (position != INVALID_TIME) { - seek(player.getCurrentPosition() + delta); - } - } + /** + * Seek a specific position from the current position + * + * @param delta + * offset from current position (positive or negative) + * */ + public void seekDelta(int delta) { + int position = getCurrentPositionSafe(); + if (position != INVALID_TIME) { + seek(player.getCurrentPosition() + delta); + } + } - public void seek(int i) { - saveCurrentPosition(); - if (status == PlayerStatus.INITIALIZED - || status == PlayerStatus.INITIALIZING - || status == PlayerStatus.PREPARING) { - media.setPosition(i); - setStartWhenPrepared(true); - prepare(); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Seeking position " + i); - if (shouldStream) { - if (status != PlayerStatus.SEEKING) { - statusBeforeSeek = status; - } - setStatus(PlayerStatus.SEEKING); - } - player.seekTo(i); - } - } + public void seek(int i) { + saveCurrentPosition(); + if (status == PlayerStatus.INITIALIZED + || status == PlayerStatus.INITIALIZING + || status == PlayerStatus.PREPARING) { + media.setPosition(i); + setStartWhenPrepared(true); + prepare(); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "Seeking position " + i); + if (shouldStream) { + if (status != PlayerStatus.SEEKING) { + statusBeforeSeek = status; + } + setStatus(PlayerStatus.SEEKING); + } + player.seekTo(i); + } + } - public void seekToChapter(Chapter chapter) { - seek((int) chapter.getStart()); - } + public void seekToChapter(Chapter chapter) { + seek((int) chapter.getStart()); + } - /** - * Saves the current position of the media file to the DB - */ - private synchronized void saveCurrentPosition() { - int position = getCurrentPositionSafe(); - if (position != INVALID_TIME) { - if (AppConfig.DEBUG) - Log.d(TAG, "Saving current position to " + position); - media.saveCurrentPosition(PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()), - position); - } - } + /** Saves the current position of the media file to the DB */ + private synchronized void saveCurrentPosition() { + int position = getCurrentPositionSafe(); + if (position != INVALID_TIME) { + if (AppConfig.DEBUG) + Log.d(TAG, "Saving current position to " + position); + media.saveCurrentPosition(PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()), + position); + } + } - private void stopWidgetUpdater() { - if (widgetUpdaterFuture != null) { - if (AppConfig.DEBUG) - Log.d(TAG, "Stopping widgetUpdateWorker"); - widgetUpdaterFuture.cancel(true); - } - sendBroadcast(new Intent(PlayerWidget.STOP_WIDGET_UPDATE)); - } + private void stopWidgetUpdater() { + if (widgetUpdaterFuture != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Stopping widgetUpdateWorker"); + widgetUpdaterFuture.cancel(true); + } + sendBroadcast(new Intent(PlayerWidget.STOP_WIDGET_UPDATE)); + } - @SuppressLint("NewApi") - private void setupWidgetUpdater() { - if (widgetUpdaterFuture == null - || (widgetUpdaterFuture.isCancelled() || widgetUpdaterFuture - .isDone())) { - widgetUpdater = new WidgetUpdateWorker(); - widgetUpdaterFuture = schedExecutor.scheduleAtFixedRate( - widgetUpdater, WidgetUpdateWorker.NOTIFICATION_INTERVALL, - WidgetUpdateWorker.NOTIFICATION_INTERVALL, - TimeUnit.MILLISECONDS); - } - } + @SuppressLint("NewApi") + private void setupWidgetUpdater() { + if (widgetUpdaterFuture == null + || (widgetUpdaterFuture.isCancelled() || widgetUpdaterFuture + .isDone())) { + widgetUpdater = new WidgetUpdateWorker(); + widgetUpdaterFuture = schedExecutor.scheduleAtFixedRate( + widgetUpdater, WidgetUpdateWorker.NOTIFICATION_INTERVALL, + WidgetUpdateWorker.NOTIFICATION_INTERVALL, + TimeUnit.MILLISECONDS); + } + } - private void updateWidget() { - if (AppConfig.DEBUG) - Log.d(TAG, "Sending widget update request"); - PlaybackService.this.sendBroadcast(new Intent( - PlayerWidget.FORCE_WIDGET_UPDATE)); - } + private void updateWidget() { + if (AppConfig.DEBUG) + Log.d(TAG, "Sending widget update request"); + PlaybackService.this.sendBroadcast(new Intent( + PlayerWidget.FORCE_WIDGET_UPDATE)); + } - public boolean sleepTimerActive() { - return sleepTimer != null && sleepTimer.isWaiting(); - } + public boolean sleepTimerActive() { + return sleepTimer != null && sleepTimer.isWaiting(); + } - public long getSleepTimerTimeLeft() { - if (sleepTimerActive()) { - return sleepTimer.getWaitingTime(); - } else { - return 0; - } - } + public long getSleepTimerTimeLeft() { + if (sleepTimerActive()) { + return sleepTimer.getWaitingTime(); + } else { + return 0; + } + } - @SuppressLint("NewApi") - private RemoteControlClient setupRemoteControlClient() { - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(mediaButtonReceiver); - PendingIntent mediaPendingIntent = PendingIntent.getBroadcast( - getApplicationContext(), 0, mediaButtonIntent, 0); - remoteControlClient = new RemoteControlClient(mediaPendingIntent); - int controlFlags; - if (android.os.Build.VERSION.SDK_INT < 16) { - controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE - | RemoteControlClient.FLAG_KEY_MEDIA_NEXT; - } else { - controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; - } - remoteControlClient.setTransportControlFlags(controlFlags); - return remoteControlClient; - } + @SuppressLint("NewApi") + private RemoteControlClient setupRemoteControlClient() { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(mediaButtonReceiver); + PendingIntent mediaPendingIntent = PendingIntent.getBroadcast( + getApplicationContext(), 0, mediaButtonIntent, 0); + remoteControlClient = new RemoteControlClient(mediaPendingIntent); + int controlFlags; + if (android.os.Build.VERSION.SDK_INT < 16) { + controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE + | RemoteControlClient.FLAG_KEY_MEDIA_NEXT; + } else { + controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; + } + remoteControlClient.setTransportControlFlags(controlFlags); + return remoteControlClient; + } - /** - * Refresh player status and metadata. - */ - @SuppressLint("NewApi") - private void refreshRemoteControlClientState() { - if (android.os.Build.VERSION.SDK_INT >= 14) { - if (remoteControlClient != null) { - switch (status) { - case PLAYING: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); - break; - case PAUSED: - case INITIALIZED: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED); - break; - case STOPPED: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); - break; - case ERROR: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_ERROR); - break; - default: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_BUFFERING); - } - if (media != null) { - MetadataEditor editor = remoteControlClient - .editMetadata(false); - editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, - media.getEpisodeTitle()); + /** Refresh player status and metadata. */ + @SuppressLint("NewApi") + private void refreshRemoteControlClientState() { + if (android.os.Build.VERSION.SDK_INT >= 14) { + if (remoteControlClient != null) { + switch (status) { + case PLAYING: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); + break; + case PAUSED: + case INITIALIZED: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED); + break; + case STOPPED: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); + break; + case ERROR: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_ERROR); + break; + default: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_BUFFERING); + } + if (media != null) { + MetadataEditor editor = remoteControlClient + .editMetadata(false); + editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, + media.getEpisodeTitle()); - editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, - media.getFeedTitle()); + editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, + media.getFeedTitle()); - editor.apply(); - } - if (AppConfig.DEBUG) - Log.d(TAG, "RemoteControlClient state was refreshed"); - } - } - } + editor.apply(); + } + if (AppConfig.DEBUG) + Log.d(TAG, "RemoteControlClient state was refreshed"); + } + } + } - private void bluetoothNotifyChange() { - boolean isPlaying = false; + private void bluetoothNotifyChange() { + boolean isPlaying = false; - if (status == PlayerStatus.PLAYING) { - isPlaying = true; - } + if (status == PlayerStatus.PLAYING) { + isPlaying = true; + } - Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); - i.putExtra("id", 1); - i.putExtra("artist", ""); - i.putExtra("album", media.getFeedTitle()); - i.putExtra("track", media.getEpisodeTitle()); - i.putExtra("playing", isPlaying); + Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); + i.putExtra("id", 1); + i.putExtra("artist", ""); + i.putExtra("album", media.getFeedTitle()); + i.putExtra("track", media.getEpisodeTitle()); + i.putExtra("playing", isPlaying); if (queue != null) { i.putExtra("ListSize", queue.size()); } i.putExtra("duration", media.getDuration()); - i.putExtra("position", media.getPosition()); - sendBroadcast(i); - } + i.putExtra("position", media.getPosition()); + sendBroadcast(i); + } - /** - * Pauses playback when the headset is disconnected and the preference is - * set - */ - private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() { - private static final String TAG = "headsetDisconnected"; - private static final int UNPLUGGED = 0; + /** + * Pauses playback when the headset is disconnected and the preference is + * set + */ + private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() { + private static final String TAG = "headsetDisconnected"; + private static final int UNPLUGGED = 0; - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) { - int state = intent.getIntExtra("state", -1); - if (state != -1) { - if (AppConfig.DEBUG) - Log.d(TAG, "Headset plug event. State is " + state); - if (state == UNPLUGGED && status == PlayerStatus.PLAYING) { - if (AppConfig.DEBUG) - Log.d(TAG, "Headset was unplugged during playback."); - pauseIfPauseOnDisconnect(); - } - } else { - Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent"); - } - } - } - }; + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) { + int state = intent.getIntExtra("state", -1); + if (state != -1) { + if (AppConfig.DEBUG) + Log.d(TAG, "Headset plug event. State is " + state); + if (state == UNPLUGGED && status == PlayerStatus.PLAYING) { + if (AppConfig.DEBUG) + Log.d(TAG, "Headset was unplugged during playback."); + pauseIfPauseOnDisconnect(); + } + } else { + Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent"); + } + } + } + }; - private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() { + private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // sound is about to change, eg. bluetooth -> speaker - if (AppConfig.DEBUG) - Log.d(TAG, "Pausing playback because audio is becoming noisy"); - pauseIfPauseOnDisconnect(); - } - // android.media.AUDIO_BECOMING_NOISY - }; + @Override + public void onReceive(Context context, Intent intent) { + // sound is about to change, eg. bluetooth -> speaker + if (AppConfig.DEBUG) + Log.d(TAG, "Pausing playback because audio is becoming noisy"); + pauseIfPauseOnDisconnect(); + } + // android.media.AUDIO_BECOMING_NOISY + }; - /** - * Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. - */ - private void pauseIfPauseOnDisconnect() { - if (UserPreferences.isPauseOnHeadsetDisconnect() - && status == PlayerStatus.PLAYING) { - pause(true, true); - } - } + /** Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. */ + private void pauseIfPauseOnDisconnect() { + if (UserPreferences.isPauseOnHeadsetDisconnect() + && status == PlayerStatus.PLAYING) { + pause(true, true); + } + } - private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { + private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { - schedExecutor.shutdownNow(); - stop(); - media = null; - } - } + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { + schedExecutor.shutdownNow(); + stop(); + media = null; + } + } - }; + }; - private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(ACTION_SKIP_CURRENT_EPISODE)) { + private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(ACTION_SKIP_CURRENT_EPISODE)) { - if (AppConfig.DEBUG) - Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent"); - if (media != null) { - setStatus(PlayerStatus.STOPPED); - player.reset(); - endPlayback(false); - } - } - } - }; + if (AppConfig.DEBUG) + Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent"); + if (media != null) { + setStatus(PlayerStatus.STOPPED); + player.reset(); + endPlayback(false); + } + } + } + }; - /** - * Periodically saves the position of the media file - */ - class PositionSaver implements Runnable { - public static final int WAITING_INTERVALL = 5000; + /** Periodically saves the position of the media file */ + class PositionSaver implements Runnable { + public static final int WAITING_INTERVALL = 5000; - @Override - public void run() { - if (player != null && player.isPlaying()) { - try { - saveCurrentPosition(); - } catch (IllegalStateException e) { - Log.w(TAG, - "saveCurrentPosition was called in illegal state"); - } - } - } - } + @Override + public void run() { + if (player != null && player.isPlaying()) { + try { + saveCurrentPosition(); + } catch (IllegalStateException e) { + Log.w(TAG, + "saveCurrentPosition was called in illegal state"); + } + } + } + } - /** - * Notifies the player widget in the specified intervall - */ - class WidgetUpdateWorker implements Runnable { - private static final int NOTIFICATION_INTERVALL = 1000; + /** Notifies the player widget in the specified intervall */ + class WidgetUpdateWorker implements Runnable { + private static final int NOTIFICATION_INTERVALL = 1000; - @Override - public void run() { - if (PlaybackService.isRunning) { - updateWidget(); - } - } - } + @Override + public void run() { + if (PlaybackService.isRunning) { + updateWidget(); + } + } + } - /** - * Sleeps for a given time and then pauses playback. - */ - class SleepTimer implements Runnable { - private static final String TAG = "SleepTimer"; - private static final long UPDATE_INTERVALL = 1000L; - private volatile long waitingTime; - private boolean isWaiting; + /** Sleeps for a given time and then pauses playback. */ + class SleepTimer implements Runnable { + private static final String TAG = "SleepTimer"; + private static final long UPDATE_INTERVALL = 1000L; + private volatile long waitingTime; + private boolean isWaiting; - public SleepTimer(long waitingTime) { - super(); - this.waitingTime = waitingTime; - } + public SleepTimer(long waitingTime) { + super(); + this.waitingTime = waitingTime; + } - @Override - public void run() { - isWaiting = true; - if (AppConfig.DEBUG) - Log.d(TAG, "Starting"); - while (waitingTime > 0) { - try { - Thread.sleep(UPDATE_INTERVALL); - waitingTime -= UPDATE_INTERVALL; + @Override + public void run() { + isWaiting = true; + if (AppConfig.DEBUG) + Log.d(TAG, "Starting"); + while (waitingTime > 0) { + try { + Thread.sleep(UPDATE_INTERVALL); + waitingTime -= UPDATE_INTERVALL; - if (waitingTime <= 0) { - if (AppConfig.DEBUG) - Log.d(TAG, "Waiting completed"); - if (status == PlayerStatus.PLAYING) { - if (AppConfig.DEBUG) - Log.d(TAG, "Pausing playback"); - pause(true, true); - } - postExecute(); - } - } catch (InterruptedException e) { - Log.d(TAG, "Thread was interrupted while waiting"); - break; - } - } - postExecute(); - } + if (waitingTime <= 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Waiting completed"); + if (status == PlayerStatus.PLAYING) { + if (AppConfig.DEBUG) + Log.d(TAG, "Pausing playback"); + pause(true, true); + } + postExecute(); + } + } catch (InterruptedException e) { + Log.d(TAG, "Thread was interrupted while waiting"); + break; + } + } + postExecute(); + } - protected void postExecute() { - isWaiting = false; - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } + protected void postExecute() { + isWaiting = false; + sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); + } - public long getWaitingTime() { - return waitingTime; - } + public long getWaitingTime() { + return waitingTime; + } - public boolean isWaiting() { - return isWaiting; - } + public boolean isWaiting() { + return isWaiting; + } - } + } - public static boolean isPlayingVideo() { - return playingVideo; - } + public static boolean isPlayingVideo() { + return playingVideo; + } - public boolean isShouldStream() { - return shouldStream; - } + public boolean isShouldStream() { + return shouldStream; + } - public PlayerStatus getStatus() { - return status; - } + public PlayerStatus getStatus() { + return status; + } - public Playable getMedia() { - return media; - } + public Playable getMedia() { + return media; + } - public MediaPlayer getPlayer() { - return player; - } + public MediaPlayer getPlayer() { + return player; + } - public boolean isStartWhenPrepared() { - return startWhenPrepared; - } + public boolean isStartWhenPrepared() { + return startWhenPrepared; + } - public void setStartWhenPrepared(boolean startWhenPrepared) { - this.startWhenPrepared = startWhenPrepared; - postStatusUpdateIntent(); - } + public void setStartWhenPrepared(boolean startWhenPrepared) { + this.startWhenPrepared = startWhenPrepared; + postStatusUpdateIntent(); + } - /** - * call getDuration() on mediaplayer or return INVALID_TIME if player is in - * an invalid state. This method should be used instead of calling - * getDuration() directly to avoid an error. - */ - public int getDurationSafe() { - if (status != null && player != null) { - switch (status) { - case PREPARED: - case PLAYING: - case PAUSED: - case SEEKING: - try { - return player.getDuration(); - } catch (IllegalStateException e) { - e.printStackTrace(); - return INVALID_TIME; - } - default: - return INVALID_TIME; - } - } else { - return INVALID_TIME; - } - } + /** + * call getDuration() on mediaplayer or return INVALID_TIME if player is in + * an invalid state. This method should be used instead of calling + * getDuration() directly to avoid an error. + */ + public int getDurationSafe() { + if (status != null && player != null) { + switch (status) { + case PREPARED: + case PLAYING: + case PAUSED: + case SEEKING: + try { + return player.getDuration(); + } catch (IllegalStateException e) { + e.printStackTrace(); + return INVALID_TIME; + } + default: + return INVALID_TIME; + } + } else { + return INVALID_TIME; + } + } - /** - * call getCurrentPosition() on mediaplayer or return INVALID_TIME if player - * is in an invalid state. This method should be used instead of calling - * getCurrentPosition() directly to avoid an error. - */ - public int getCurrentPositionSafe() { - if (status != null && player != null) { - switch (status) { - case PREPARED: - case PLAYING: - case PAUSED: - case SEEKING: - return player.getCurrentPosition(); - default: - return INVALID_TIME; - } - } else { - return INVALID_TIME; - } - } + /** + * call getCurrentPosition() on mediaplayer or return INVALID_TIME if player + * is in an invalid state. This method should be used instead of calling + * getCurrentPosition() directly to avoid an error. + */ + public int getCurrentPositionSafe() { + if (status != null && player != null) { + switch (status) { + case PREPARED: + case PLAYING: + case PAUSED: + case SEEKING: + return player.getCurrentPosition(); + default: + return INVALID_TIME; + } + } else { + return INVALID_TIME; + } + } - private void setCurrentlyPlayingMedia(long id) { - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, id); - editor.commit(); - } + private void setCurrentlyPlayingMedia(long id) { + SharedPreferences.Editor editor = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()).edit(); + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, id); + editor.commit(); + } - private static class InitTask extends AsyncTask { - private Playable playable; - public PlayableException exception; + private static class InitTask extends AsyncTask { + private Playable playable; + public PlayableException exception; - @Override - protected Playable doInBackground(Playable... params) { - if (params[0] == null) { - throw new IllegalArgumentException("Playable must not be null"); - } - playable = params[0]; + @Override + protected Playable doInBackground(Playable... params) { + if (params[0] == null) { + throw new IllegalArgumentException("Playable must not be null"); + } + playable = params[0]; - try { - playable.loadMetadata(); - } catch (PlayableException e) { - e.printStackTrace(); - exception = e; - return null; - } - return playable; - } + try { + playable.loadMetadata(); + } catch (PlayableException e) { + e.printStackTrace(); + exception = e; + return null; + } + return playable; + } - @SuppressLint("NewApi") - public void executeAsync(Playable playable) { - FlattrUtils.hasToken(); - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - executeOnExecutor(THREAD_POOL_EXECUTOR, playable); - } else { - execute(playable); - } - } + @SuppressLint("NewApi") + public void executeAsync(Playable playable) { + FlattrUtils.hasToken(); + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + executeOnExecutor(THREAD_POOL_EXECUTOR, playable); + } else { + execute(playable); + } + } - } + } private void loadQueue() { dbLoaderExecutor.submit(new QueueLoaderTask()); diff --git a/src/de/danoeh/antennapod/service/download/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java index b19b0482e..2056efab2 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadService.java +++ b/src/de/danoeh/antennapod/service/download/DownloadService.java @@ -608,7 +608,7 @@ public class DownloadService extends Service { private DownloadRequest request; - private int reason; + private DownloadError reason; private boolean successful; public FeedSyncThread(DownloadRequest request) { @@ -626,7 +626,7 @@ public class DownloadService extends Service { feed.setFile_url(request.getDestination()); feed.setDownloaded(true); - reason = 0; + reason = null; String reasonDetailed = null; successful = true; FeedHandler feedHandler = new FeedHandler(); diff --git a/src/de/danoeh/antennapod/service/download/DownloadStatus.java b/src/de/danoeh/antennapod/service/download/DownloadStatus.java index 76091ec67..62e54cbb4 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadStatus.java +++ b/src/de/danoeh/antennapod/service/download/DownloadStatus.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.service.download; import java.util.Date; import de.danoeh.antennapod.feed.FeedFile; +import de.danoeh.antennapod.util.DownloadError; /** Contains status attributes for one download */ public class DownloadStatus { @@ -21,7 +22,7 @@ public class DownloadStatus { * URL if the download has no other title. */ protected String title; - protected int reason; + protected DownloadError reason; /** * A message which can be presented to the user to give more information. * Should be null if Download was successful. @@ -43,7 +44,7 @@ public class DownloadStatus { /** Constructor for restoring Download status entries from DB. */ public DownloadStatus(long id, String title, long feedfileId, - int feedfileType, boolean successful, int reason, + int feedfileType, boolean successful, DownloadError reason, Date completionDate, String reasonDetailed) { this.id = id; this.title = title; @@ -56,7 +57,7 @@ public class DownloadStatus { this.feedfileType = feedfileType; } - public DownloadStatus(DownloadRequest request, int reason, + public DownloadStatus(DownloadRequest request, DownloadError reason, boolean successful, boolean cancelled, String reasonDetailed) { if (request == null) { throw new IllegalArgumentException("request must not be null"); @@ -72,7 +73,7 @@ public class DownloadStatus { } /** Constructor for creating new completed downloads. */ - public DownloadStatus(FeedFile feedfile, String title, int reason, + public DownloadStatus(FeedFile feedfile, String title, DownloadError reason, boolean successful, String reasonDetailed) { if (feedfile == null) { throw new IllegalArgumentException("feedfile must not be null"); @@ -90,7 +91,7 @@ public class DownloadStatus { /** Constructor for creating new completed downloads. */ public DownloadStatus(long feedfileId, int feedfileType, String title, - int reason, boolean successful, String reasonDetailed) { + DownloadError reason, boolean successful, String reasonDetailed) { this.title = title; this.done = true; this.feedfileId = feedfileId; @@ -111,48 +112,70 @@ public class DownloadStatus { + ", cancelled=" + cancelled + "]"; } - public long getId() { - return id; - } + public long getId() { + return id; + } - public String getTitle() { - return title; - } + public String getTitle() { + return title; + } - public int getReason() { - return reason; - } + public DownloadError getReason() { + return reason; + } - public String getReasonDetailed() { - return reasonDetailed; - } + public String getReasonDetailed() { + return reasonDetailed; + } - public boolean isSuccessful() { - return successful; - } + public boolean isSuccessful() { + return successful; + } - public Date getCompletionDate() { - return completionDate; - } + public Date getCompletionDate() { + return completionDate; + } - public long getFeedfileId() { - return feedfileId; - } + public long getFeedfileId() { + return feedfileId; + } - public int getFeedfileType() { - return feedfileType; - } + public int getFeedfileType() { + return feedfileType; + } - public boolean isDone() { - return done; - } + public boolean isDone() { + return done; + } - public boolean isCancelled() { - return cancelled; - } + public boolean isCancelled() { + return cancelled; + } - public void setId(long id) { - this.id = id; - } + public void setSuccessful() { + this.successful = true; + this.reason = DownloadError.SUCCESS; + this.done = true; + } + public void setFailed(DownloadError reason, String reasonDetailed) { + this.successful = false; + this.reason = reason; + this.reasonDetailed = reasonDetailed; + } + + public void setCancelled() { + this.successful = false; + this.reason = DownloadError.ERROR_DOWNLOAD_CANCELLED; + this.done = true; + this.cancelled = true; + } + + public void setCompletionDate(Date completionDate) { + this.completionDate = completionDate; + } + + public void setId(long id) { + this.id = id; + } } \ No newline at end of file diff --git a/src/de/danoeh/antennapod/service/download/Downloader.java b/src/de/danoeh/antennapod/service/download/Downloader.java index 67507d40f..84731fe9f 100644 --- a/src/de/danoeh/antennapod/service/download/Downloader.java +++ b/src/de/danoeh/antennapod/service/download/Downloader.java @@ -20,6 +20,7 @@ public abstract class Downloader implements Callable { this.request = request; this.request.setStatusMsg(R.string.download_pending); this.cancelled = false; + this.result = new DownloadStatus(request, null, false, false, null); } protected abstract void download(); diff --git a/src/de/danoeh/antennapod/service/download/HttpDownloader.java b/src/de/danoeh/antennapod/service/download/HttpDownloader.java index b533ca676..c9671ceb3 100644 --- a/src/de/danoeh/antennapod/service/download/HttpDownloader.java +++ b/src/de/danoeh/antennapod/service/download/HttpDownloader.java @@ -153,23 +153,21 @@ public class HttpDownloader extends Downloader { private void onSuccess() { if (AppConfig.DEBUG) Log.d(TAG, "Download was successful"); - result = new DownloadStatus(request, 0, true, false, null); + result.setSuccessful(); } - private void onFail(int reason, String reasonDetailed) { + private void onFail(DownloadError reason, String reasonDetailed) { if (AppConfig.DEBUG) { Log.d(TAG, "Download failed"); } - result = new DownloadStatus(request, reason, false, false, - reasonDetailed); + result.setFailed(reason, reasonDetailed); cleanup(); } private void onCancelled() { if (AppConfig.DEBUG) Log.d(TAG, "Download was cancelled"); - result = new DownloadStatus(request, - DownloadError.ERROR_DOWNLOAD_CANCELLED, false, true, null); + result.setCancelled(); cleanup(); } diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index ababcdf78..4f08c2b00 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -19,6 +19,7 @@ import de.danoeh.antennapod.feed.ID3Chapter; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.feed.VorbisCommentChapter; import de.danoeh.antennapod.service.download.*; +import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; @@ -428,7 +429,7 @@ public final class DBReader { logCursor .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX)); downloadLog.add(new DownloadStatus(id, title, feedfileId, - feedfileType, successful, reason, completionDate, + feedfileType, successful, DownloadError.fromCode(reason), completionDate, reasonDetailed)); } while (logCursor.moveToNext()); diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 5171b6932..5718e03c0 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -519,7 +519,7 @@ public class PodDBAdapter { ContentValues values = new ContentValues(); values.put(KEY_FEEDFILE, status.getFeedfileId()); values.put(KEY_FEEDFILETYPE, status.getFeedfileType()); - values.put(KEY_REASON, status.getReason()); + values.put(KEY_REASON, status.getReason().getCode()); values.put(KEY_SUCCESSFUL, status.isSuccessful()); values.put(KEY_COMPLETION_DATE, status.getCompletionDate().getTime()); values.put(KEY_REASON_DETAILED, status.getReasonDetailed()); diff --git a/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java b/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java index 4d0b42132..5a2c6005e 100644 --- a/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java +++ b/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java @@ -78,6 +78,15 @@ public class NSRSS20 extends Namespace { @Override public void handleElementEnd(String localName, HandlerState state) { if (localName.equals(ITEM)) { + if (state.getCurrentItem() != null) { + // the title tag is optional in RSS 2.0. The description is used + // as a + // title if the item has no title-tag. + if (state.getCurrentItem().getTitle() == null) { + state.getCurrentItem().setTitle( + state.getCurrentItem().getDescription()); + } + } state.setCurrentItem(null); } else if (state.getTagstack().size() >= 2 && state.getContentBuf() != null) { @@ -98,7 +107,8 @@ public class NSRSS20 extends Namespace { state.getCurrentItem().setTitle(content); } else if (second.equals(CHANNEL)) { state.getFeed().setTitle(content); - } else if (second.equals(IMAGE) && third != null && third.equals(CHANNEL)) { + } else if (second.equals(IMAGE) && third != null + && third.equals(CHANNEL)) { state.getFeed().getImage().setTitle(content); } } else if (top.equals(LINK)) { @@ -110,7 +120,8 @@ public class NSRSS20 extends Namespace { } else if (top.equals(PUBDATE) && second.equals(ITEM)) { state.getCurrentItem().setPubDate( SyndDateUtils.parseRFC822Date(content)); - } else if (top.equals(URL) && second.equals(IMAGE) && third != null && third.equals(CHANNEL)) { + } else if (top.equals(URL) && second.equals(IMAGE) && third != null + && third.equals(CHANNEL)) { state.getFeed().getImage().setDownload_url(content); } else if (localName.equals(DESCR)) { if (second.equals(CHANNEL)) { diff --git a/src/de/danoeh/antennapod/util/ConnectionTester.java b/src/de/danoeh/antennapod/util/ConnectionTester.java index 2fd22d356..5d940d9e1 100644 --- a/src/de/danoeh/antennapod/util/ConnectionTester.java +++ b/src/de/danoeh/antennapod/util/ConnectionTester.java @@ -14,7 +14,7 @@ public class ConnectionTester implements Runnable { private static final String TAG = "ConnectionTester"; private String strUrl; private Callback callback; - private int reason; + private DownloadError reason; private Handler handler; @@ -68,10 +68,10 @@ public class ConnectionTester implements Runnable { public static abstract class Callback { public abstract void onConnectionSuccessful(); - public abstract void onConnectionFailure(int reason); + public abstract void onConnectionFailure(DownloadError reason); } - public int getReason() { + public DownloadError getReason() { return reason; } diff --git a/src/de/danoeh/antennapod/util/DownloadError.java b/src/de/danoeh/antennapod/util/DownloadError.java index 4723a521c..c37a14584 100644 --- a/src/de/danoeh/antennapod/util/DownloadError.java +++ b/src/de/danoeh/antennapod/util/DownloadError.java @@ -4,54 +4,46 @@ import android.content.Context; import de.danoeh.antennapod.R; /** Utility class for Download Errors. */ -public class DownloadError { - public static final int ERROR_PARSER_EXCEPTION = 1; - public static final int ERROR_UNSUPPORTED_TYPE = 2; - public static final int ERROR_CONNECTION_ERROR = 3; - public static final int ERROR_MALFORMED_URL = 4; - public static final int ERROR_IO_ERROR = 5; - public static final int ERROR_FILE_EXISTS = 6; - public static final int ERROR_DOWNLOAD_CANCELLED = 7; - public static final int ERROR_DEVICE_NOT_FOUND = 8; - public static final int ERROR_HTTP_DATA_ERROR = 9; - public static final int ERROR_NOT_ENOUGH_SPACE = 10; - public static final int ERROR_UNKNOWN_HOST = 11; - public static final int ERROR_REQUEST_ERROR = 12; - - /** Get a human-readable string for a specific error code. */ - public static String getErrorString(Context context, int code) { - int resId; - switch(code) { - case ERROR_NOT_ENOUGH_SPACE: - resId = R.string.download_error_insufficient_space; - break; - case ERROR_DEVICE_NOT_FOUND: - resId = R.string.download_error_device_not_found; - break; - case ERROR_IO_ERROR: - resId = R.string.download_error_io_error; - break; - case ERROR_HTTP_DATA_ERROR: - resId = R.string.download_error_http_data_error; - break; - case ERROR_PARSER_EXCEPTION: - resId = R.string.download_error_parser_exception; - break; - case ERROR_UNSUPPORTED_TYPE: - resId = R.string.download_error_unsupported_type; - break; - case ERROR_CONNECTION_ERROR: - resId = R.string.download_error_connection_error; - break; - case ERROR_UNKNOWN_HOST: - resId = R.string.download_error_unknown_host; - break; - case ERROR_REQUEST_ERROR: - resId = R.string.download_error_request_error; - break; - default: - resId = R.string.download_error_error_unknown; +public enum DownloadError { + SUCCESS(0, R.string.download_successful), + ERROR_PARSER_EXCEPTION(1, R.string.download_error_parser_exception), + ERROR_UNSUPPORTED_TYPE(2, R.string.download_error_unsupported_type), + ERROR_CONNECTION_ERROR(3, R.string.download_error_connection_error), + ERROR_MALFORMED_URL(4, R.string.download_error_error_unknown), + ERROR_IO_ERROR(5, R.string.download_error_io_error), + ERROR_FILE_EXISTS(6, R.string.download_error_error_unknown), + ERROR_DOWNLOAD_CANCELLED(7, R.string.download_error_error_unknown), + ERROR_DEVICE_NOT_FOUND(8, R.string.download_error_device_not_found), + ERROR_HTTP_DATA_ERROR(9, R.string.download_error_http_data_error), + ERROR_NOT_ENOUGH_SPACE(10, R.string.download_error_insufficient_space), + ERROR_UNKNOWN_HOST(11, R.string.download_error_unknown_host), + ERROR_REQUEST_ERROR(12, R.string.download_error_request_error); + + private final int code; + private final int resId; + + private DownloadError(int code, int resId) { + this.code = code; + this.resId = resId; + } + + /** Return DownloadError from its associated code. */ + public static DownloadError fromCode(int code) { + for (DownloadError reason : values()) { + if (reason.getCode() == code) { + return reason; + } } + throw new IllegalArgumentException("unknown code: " + code); + } + + /** Get machine-readable code. */ + public int getCode() { + return code; + } + + /** Get a human-readable string. */ + public String getErrorString(Context context) { return context.getString(resId); } diff --git a/src/de/danoeh/antennapod/util/UndoBarController.java b/src/de/danoeh/antennapod/util/UndoBarController.java index e726717a1..a0240e7ce 100644 --- a/src/de/danoeh/antennapod/util/UndoBarController.java +++ b/src/de/danoeh/antennapod/util/UndoBarController.java @@ -16,15 +16,17 @@ package de.danoeh.antennapod.util; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.text.TextUtils; import android.view.View; -import android.view.ViewPropertyAnimator; import android.widget.TextView; +import com.nineoldandroids.animation.Animator; +import com.nineoldandroids.animation.AnimatorListenerAdapter; +import com.nineoldandroids.view.ViewHelper; +import com.nineoldandroids.view.ViewPropertyAnimator; +import static com.nineoldandroids.view.ViewPropertyAnimator.animate; import de.danoeh.antennapod.R; @@ -46,7 +48,7 @@ public class UndoBarController { public UndoBarController(View undoBarView, UndoListener undoListener) { mBarView = undoBarView; - mBarAnimator = mBarView.animate(); + mBarAnimator = animate(mBarView); mUndoListener = undoListener; mMessageView = (TextView) mBarView.findViewById(R.id.undobar_message); @@ -73,7 +75,7 @@ public class UndoBarController { mBarView.setVisibility(View.VISIBLE); if (immediate) { - mBarView.setAlpha(1); + ViewHelper.setAlpha(mBarView, 1); } else { mBarAnimator.cancel(); mBarAnimator @@ -89,7 +91,7 @@ public class UndoBarController { mHideHandler.removeCallbacks(mHideRunnable); if (immediate) { mBarView.setVisibility(View.GONE); - mBarView.setAlpha(0); + ViewHelper.setAlpha(mBarView, 0); mUndoMessage = null; mUndoToken = null; diff --git a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java index e1cafe85d..f897f886c 100644 --- a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java +++ b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java @@ -2,18 +2,24 @@ package de.danoeh.antennapod.util.id3reader; import java.io.IOException; import java.io.InputStream; +import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; +import android.util.Log; + +import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.ID3Chapter; import de.danoeh.antennapod.util.id3reader.model.FrameHeader; import de.danoeh.antennapod.util.id3reader.model.TagHeader; public class ChapterReader extends ID3Reader { + private static final String TAG = "ID3ChapterReader"; private static final String FRAME_ID_CHAPTER = "CHAP"; private static final String FRAME_ID_TITLE = "TIT2"; + private static final String FRAME_ID_LINK = "WXXX"; private List chapters; private ID3Chapter currentChapter; @@ -33,27 +39,45 @@ public class ChapterReader extends ID3Reader { if (currentChapter != null) { if (!hasId3Chapter(currentChapter)) { chapters.add(currentChapter); - System.out.println("Found chapter: " + currentChapter); + if (AppConfig.DEBUG) Log.d(TAG, "Found chapter: " + currentChapter); currentChapter = null; } } - String elementId = readISOString(input, Integer.MAX_VALUE); + StringBuffer elementId = new StringBuffer(); + readISOString(elementId, input, Integer.MAX_VALUE); char[] startTimeSource = readBytes(input, 4); long startTime = ((int) startTimeSource[0] << 24) | ((int) startTimeSource[1] << 16) | ((int) startTimeSource[2] << 8) | startTimeSource[3]; - currentChapter = new ID3Chapter(elementId, startTime); + currentChapter = new ID3Chapter(elementId.toString(), startTime); skipBytes(input, 12); return ID3Reader.ACTION_DONT_SKIP; } else if (header.getId().equals(FRAME_ID_TITLE)) { if (currentChapter != null && currentChapter.getTitle() == null) { + StringBuffer title = new StringBuffer(); + readString(title, input, header.getSize()); currentChapter - .setTitle(readString(input, header.getSize())); - System.out.println("Found title: " + currentChapter.getTitle()); + .setTitle(title.toString()); + if (AppConfig.DEBUG) Log.d(TAG, "Found title: " + currentChapter.getTitle()); return ID3Reader.ACTION_DONT_SKIP; } - } + } else if (header.getId().equals(FRAME_ID_LINK)) { + if (currentChapter != null) { + // skip description + int descriptionLength = readString(null, input, header.getSize()); + StringBuffer link = new StringBuffer(); + readISOString(link, input, header.getSize() - descriptionLength); + String decodedLink = URLDecoder.decode(link.toString(), "UTF-8"); + + currentChapter.setLink(decodedLink); + + if (AppConfig.DEBUG) Log.d(TAG, "Found link: " + currentChapter.getLink()); + return ID3Reader.ACTION_DONT_SKIP; + } + } else if (header.getId().equals("APIC")) { + Log.d(TAG, header.toString()); + } return super.onStartFrameHeader(header, input); } diff --git a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java index dff6d77e8..92f817363 100644 --- a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java +++ b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java @@ -24,7 +24,11 @@ public class ID3Reader { protected int readerPosition; - private static final byte ENCODING_UNICODE = 1; + private static final byte ENCODING_UTF16_WITH_BOM = 1; + private static final byte ENCODING_UTF16_WITHOUT_BOM = 2; + private static final byte ENCODING_UTF8 = 3; + + private TagHeader tagHeader; public ID3Reader() { } @@ -34,7 +38,7 @@ public class ID3Reader { int rc; readerPosition = 0; char[] tagHeaderSource = readBytes(input, HEADER_LENGTH); - TagHeader tagHeader = createTagHeader(tagHeaderSource); + tagHeader = createTagHeader(tagHeaderSource); if (tagHeader == null) { onNoTagHeaderFound(); } else { @@ -124,12 +128,12 @@ public class ID3Reader { + HEADER_LENGTH); } if (hasTag) { - String id = null; - id = new String(source, 0, ID3_LENGTH); + String id = new String(source, 0, ID3_LENGTH); char version = (char) ((source[3] << 8) | source[4]); byte flags = (byte) source[5]; int size = (source[6] << 24) | (source[7] << 16) | (source[8] << 8) | source[9]; + size = unsynchsafe(size); return new TagHeader(id, size, version, flags); } else { return null; @@ -142,48 +146,89 @@ public class ID3Reader { throw new ID3ReaderException("Length of header must be " + HEADER_LENGTH); } - String id = null; - id = new String(source, 0, FRAME_ID_LENGTH); - int size = (((int) source[4]) << 24) | (((int) source[5]) << 16) - | (((int) source[6]) << 8) | source[7]; + String id = new String(source, 0, FRAME_ID_LENGTH); + + int size = (((int) source[4]) << 24) | (((int) source[5]) << 16) + | (((int) source[6]) << 8) | source[7]; + if (tagHeader != null && tagHeader.getVersion() >= 0x0400) { + size = unsynchsafe(size); + } char flags = (char) ((source[8] << 8) | source[9]); return new FrameHeader(id, size, flags); } - protected String readString(InputStream input, int max) throws IOException, + private int unsynchsafe(int in) { + int out = 0; + int mask = 0x7F000000; + + while (mask != 0) { + out >>= 1; + out |= in & mask; + mask >>= 8; + } + + return out; + } + + protected int readString(StringBuffer buffer, InputStream input, int max) throws IOException, ID3ReaderException { if (max > 0) { char[] encoding = readBytes(input, 1); max--; - if (encoding[0] == ENCODING_UNICODE) { - return readUnicodeString(input, max); - } else { - return readISOString(input, max); + if (encoding[0] == ENCODING_UTF16_WITH_BOM || encoding[0] == ENCODING_UTF16_WITHOUT_BOM) { + return readUnicodeString(buffer, input, max, Charset.forName("UTF-16")) + 1; // take encoding byte into account + } else if (encoding[0] == ENCODING_UTF8) { + return readUnicodeString(buffer, input, max, Charset.forName("UTF-8")) + 1; // take encoding byte into account + } else { + return readISOString(buffer, input, max) + 1; // take encoding byte into account } } else { - return ""; + if (buffer != null) { + buffer.append(""); + } + return 0; } } - protected String readISOString(InputStream input, int max) + protected int readISOString(StringBuffer buffer, InputStream input, int max) throws IOException, ID3ReaderException { int bytesRead = 0; - StringBuilder builder = new StringBuilder(); char c; while (++bytesRead <= max && (c = (char) input.read()) > 0) { - builder.append(c); + if (buffer != null) { + buffer.append(c); + } } - return builder.toString(); + return bytesRead; } - private String readUnicodeString(InputStream input, int max) + private int readUnicodeString(StringBuffer strBuffer, InputStream input, int max, Charset charset) throws IOException, ID3ReaderException { byte[] buffer = new byte[max]; - IOUtils.readFully(input, buffer); - Charset charset = Charset.forName("UTF-16"); - return charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString(); + int c, cZero = -1; + int i = 0; + for (; i < max; i++) { + c = input.read(); + if (c == -1) { + break; + } else if (c == 0) { + if (cZero == 0) { + // termination character found + break; + } else { + cZero = 0; + } + } else { + buffer[i] = (byte) c; + cZero = -1; + } + } + if (strBuffer != null) { + strBuffer.append(charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString()); + } + return i; } public int onStartTagHeader(TagHeader header) { diff --git a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java index 2c0d8e5ba..df73393a5 100644 --- a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java +++ b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java @@ -11,8 +11,7 @@ public class FrameHeader extends Header { @Override public String toString() { - return "FrameHeader [flags=" + Integer.toString(flags) + ", id=" + id + ", size=" + size - + "]"; - } + return String.format("FrameHeader [flags=%s, id=%s, size=%s]", Integer.toBinaryString(flags), id, Integer.toBinaryString(size)); + } } diff --git a/src/de/danoeh/antennapod/util/playback/PlaybackController.java b/src/de/danoeh/antennapod/util/playback/PlaybackController.java index 847e08b4a..5a5b43a6e 100644 --- a/src/de/danoeh/antennapod/util/playback/PlaybackController.java +++ b/src/de/danoeh/antennapod/util/playback/PlaybackController.java @@ -44,8 +44,8 @@ import de.danoeh.antennapod.util.playback.Playable.PlayableUtils; public abstract class PlaybackController { private static final String TAG = "PlaybackController"; - static final int DEFAULT_SEEK_DELTA = 30000; - public static final int INVALID_TIME = -1; + public static final int DEFAULT_SEEK_DELTA = 30000; + public static final int INVALID_TIME = -1; private Activity activity; diff --git a/tests/.classpath b/tests/.classpath index 0e8961f49..62d428f2b 100644 --- a/tests/.classpath +++ b/tests/.classpath @@ -5,5 +5,6 @@ + diff --git a/tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java b/tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java index 132d40eba..daba95dbf 100644 --- a/tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java +++ b/tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java @@ -54,7 +54,7 @@ public class FeedHandlerTest extends AndroidTestCase { for (int i = 0; i < num_retries; i++) { InputStream in = null; - OutputStream out = null; + BufferedOutputStream out = null; try { in = getInputStream(feed.getDownload_url()); assertNotNull(in); @@ -65,6 +65,7 @@ public class FeedHandlerTest extends AndroidTestCase { while ((count = in.read(buffer)) != -1) { out.write(buffer, 0, count); } + out.flush(); successful = true; } catch (IOException e) { e.printStackTrace(); diff --git a/tests/src/de/danoeh/antennapod/test/TestFeeds.java b/tests/src/de/danoeh/antennapod/test/TestFeeds.java index 576a8ddd9..8b754fea6 100644 --- a/tests/src/de/danoeh/antennapod/test/TestFeeds.java +++ b/tests/src/de/danoeh/antennapod/test/TestFeeds.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.test; public class TestFeeds { public static final String[] urls = { + "http://savoirsenmultimedia.ens.fr/podcast.php?id=30", "http://bitlove.org/apollo40/ps3newsroom/feed", "http://bitlove.org/beapirate/hauptstadtpiraten/feed", "http://bitlove.org/benni/besondereumstaende/feed",