A|K2*!d0Yt=sz=^;Pz_K|T9t19y^kprAkphT_
z&2sAQku;)buj$0)38HEmj|_nG9XK7h05Cu5m)AYyBt2IXg;4_761W!F7We@87FY?`
zxB+OI&q(^M6htQg$#FIUK9}@!$z>672yh4RybD;;+=%d`z8^5Xwzw8fmh@4HPM_%t
zU>(z6=d&bp`c{dE!+^Uog=QFdrj_}UL6{SnZcmZ)VynVDPz@lxe>AYayNM*HL#sr@
z5x_mb%fPcq@-_svw$TZ|j~+AA2U-;l0ATIx7_R~|CAovNN&|q;E&!Y+lk>0X$kQav
zXjM1>fW0$)&X@F9=|vGS6}U5d|DrzgJaBpzNmU9(#PTk(973l^dbj8nAR;!&8L&^w
zH`hq=9YsrUn`EC59q~l5z*G9nodM0qjL_bU-!cn=}b7GhPjs;Eyoc|A(
zda2}GMC_FjczT!s&IOJ%2uTl>7*={FB96-TJWG-v{zX!oLM~umr(Oo&9prhp&*
zj>2V<&MH-03*gdZl-{gOljJ?mKoSvaWnCSRoyk1u=2E4#0NkJ5DLj3r=f-VbbMIMl
zagsQ1Ds}B32dLaynE($8xYsWTlpTa
z(QoDt8>ZKtq!WPNP04Fqq%KgiO?Q(uvRIvN&Ta2|<|Y6CFpJ#{U_sZ7^gt=p$xTE2
vXrKrT(XL|EV*tgj8q&dW1cv0;Vzqw(+|Xh`^N!J|00000NkvXXu0mjf)y^n2
literal 0
HcmV?d00001
diff --git a/common/toolbar/src/main/res/drawable-hdpi/ic_speaker.png b/common/toolbar/src/main/res/drawable-hdpi/ic_speaker.png
new file mode 100644
index 0000000000000000000000000000000000000000..22d33dd552b195a4a16b663b85f1a0427452fa85
GIT binary patch
literal 1150
zcmV-^1cCdBP)Px(I7vi7RA@u(m|3iyV;F^>rDkFlW9X0w<-*K`;l@0RDG?!2lqhN*f|wFBHPjFi
z7eZRxxG+SD8%-oGL<~_N>Epg#-R6LSGhlmr
z;4I)~U{~NJN#`{IupL|W2L1%Tl9b=ZH822Wz;xg$;2dDby8U%Yr!`q(dp7WBy_es2
zB>mO2Z;$}?2Ob6v0yb;8xLW}G#b{s~;6vbAN%`%#`UYToJ>XQ}86e^F?=#y1u)Q%b
zP15hS*9YbU_XF|#s|Di6P6*lpsE}v^xH)hga09SWn~Np{U^|}v6j%ma0em6Jw&RG4
zfi=MO!1I#+Xa}MbfPH{HJD`uplB_2I8+LfF9e|x;G_V!$GH|1$mA1D8-Ubc@-UsGN
z`nneY@xRuwuEcoxVz7XpjgRi#c0Jnf(ShM$)&oX8=zF#{!FiImwfWE?SoW64&u?67(+M
z-ZJQ9;BjDgV4kFfwiA+9);qrfvnBmJYydk0PXMO_Yk^skezKjC`&fm_a$t{2=2Hsr
zF2&4}p#sR|w$BD00bd6A&wF;wOfEkij+dd9>Dt`kfN_x|Fq(~Sz
z19(AFs?nriV9Em+522OCKtd?8I}#!xbhxAss+5L-6C&~?T{CnBY^QT486&grk#ySx
zNjzQBceW!610cyfUs5X7@IuITDo@I2q;+coYK$FYj#SA3vW`cGA
zwk-f(*8a~cdBZ}4Tz>;i7ojositTAN#4iB0>vp89i7ypMEhA;*dz7h5Mu(N&M#^ph
zv}l||fQNtsDt5;obvxAWTSI-ASz7b(M@@D#g-Fx>f1Z~|XVqYyi7FK)t6Kocw}ekV
z62uWp5>ibD3=BXE6EQphZmK5qqNJ3`j;kE7SB1~AZoUcSz<~p(yvV1;d?%VyQuvc5
z0KGo{UjX!KOfUOJ0DAG-Aoq+I7^F9QIcvl~FJ2quo)H6s^ky$-O?d|X2BpK+K-#iZ
QtpET307*qoM6N<$f_?P?egFUf
literal 0
HcmV?d00001
diff --git a/common/toolbar/src/main/res/drawable-mdpi/ic_qaf.png b/common/toolbar/src/main/res/drawable-mdpi/ic_qaf.png
new file mode 100644
index 0000000000000000000000000000000000000000..51bed2d502f70542236fd8c3b421133d30292317
GIT binary patch
literal 640
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
literal 0
HcmV?d00001
diff --git a/common/toolbar/src/main/res/drawable-xhdpi/ic_speaker.png b/common/toolbar/src/main/res/drawable-xhdpi/ic_speaker.png
new file mode 100644
index 0000000000000000000000000000000000000000..3784a535a1e9b28f9377b2016bd0c0448113e40e
GIT binary patch
literal 1528
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
literal 0
HcmV?d00001
diff --git a/common/toolbar/src/main/res/drawable-xxxhdpi/ic_qaf.png b/common/toolbar/src/main/res/drawable-xxxhdpi/ic_qaf.png
new file mode 100644
index 0000000000000000000000000000000000000000..81ff927acfa729b483ef5d6e9cefc67248b197ee
GIT binary patch
literal 3202
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
literal 0
HcmV?d00001
diff --git a/common/toolbar/src/main/res/drawable-xxxhdpi/ic_speaker.png b/common/toolbar/src/main/res/drawable-xxxhdpi/ic_speaker.png
new file mode 100644
index 0000000000000000000000000000000000000000..8f09e81ad5f53ff4a9ee4cf7e42ced8ab08ec95d
GIT binary patch
literal 3451
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
F{{Wh2AKCx_
literal 0
HcmV?d00001
diff --git a/common/toolbar/src/main/res/drawable/ic_audio.xml b/common/toolbar/src/main/res/drawable/ic_audio.xml
deleted file mode 100644
index c0d2d8d87b..0000000000
--- a/common/toolbar/src/main/res/drawable/ic_audio.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
diff --git a/common/toolbar/src/main/res/menu/ayah_menu.xml b/common/toolbar/src/main/res/menu/ayah_menu.xml
index 20750b1d80..7dffef4cb9 100644
--- a/common/toolbar/src/main/res/menu/ayah_menu.xml
+++ b/common/toolbar/src/main/res/menu/ayah_menu.xml
@@ -19,11 +19,11 @@
android:title="@string/share_ayah" />
-
Date: Thu, 7 Jul 2022 14:34:50 +0300
Subject: [PATCH 05/18] proper "sharable audio" name
---
.../labs/androidquran/ui/PagerActivity.java | 37 ++++++++++++++-----
1 file changed, 27 insertions(+), 10 deletions(-)
diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
index dd2ed639fb..e925927471 100644
--- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
+++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
@@ -261,11 +261,12 @@ public class PagerActivity extends AppCompatActivity implements
private final PagerHandler handler = new PagerHandler(this);
- private Disposable timingDisposable;
- private int gaplessSura;
- private SparseIntArray gaplessSuraData = new SparseIntArray();
public static final File audioCacheDirectory= new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath() +
File.separator +"quran_android_cache");
+ private ArrayList audioCacheFilePaths = new ArrayList<>();
+ private SuraAyah selectedStartSuraAyah = null;
+ private SuraAyah selectedEndSuraAyah = null;
+ private QariItem selectedQari = null;
private static class PagerHandler extends Handler {
@@ -1864,7 +1865,7 @@ public void onError(@NonNull Throwable e) {
}
public void shareAyahAudio(SuraAyah start, SuraAyah end) {
- SuraAyah actualStart,actualEnd;
+ audioCacheFilePaths.clear();
if (start == null || end == null) {
return;
}else {
@@ -1877,16 +1878,16 @@ public void shareAyahAudio(SuraAyah start, SuraAyah end) {
}
kotlin.Pair pair2 = pair;
- actualStart = (SuraAyah) pair2.component1();
- actualEnd = (SuraAyah) pair2.component2();
+ selectedStartSuraAyah = (SuraAyah) pair2.component1();
+ selectedEndSuraAyah = (SuraAyah) pair2.component2();
}
- final QariItem qari = audioStatusBar.getAudioInfo();
- AudioPathInfo audioPathInfo = audioUtils.getLocalAudioPathInfo(this,qari);
+ selectedQari = audioStatusBar.getAudioInfo();
+ AudioPathInfo audioPathInfo = audioUtils.getLocalAudioPathInfo(this,selectedQari);
assert audioPathInfo != null;
if (audioPathInfo.getGaplessDatabase() != null) {
- createAndShareAudio(actualStart,actualEnd,audioPathInfo);
+ createAndShareAudio(selectedStartSuraAyah,selectedEndSuraAyah,audioPathInfo);
}
}
@@ -1942,7 +1943,10 @@ public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull ArrayList paths = new ArrayList<>();
String path1 = audioUtils.getSurahAudioPath(audioPathInfo,start.sura);
@@ -1963,7 +1967,9 @@ public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull ArrayList
Date: Thu, 7 Jul 2022 14:57:20 +0300
Subject: [PATCH 06/18] share intent title
---
app/src/main/java/com/quran/labs/androidquran/util/ShareUtil.kt | 2 +-
app/src/main/res/values-ar/strings.xml | 1 +
app/src/main/res/values-az/strings.xml | 1 +
app/src/main/res/values-bs/strings.xml | 1 +
app/src/main/res/values-de/strings.xml | 1 +
app/src/main/res/values-es/strings.xml | 1 +
app/src/main/res/values-fa/strings.xml | 1 +
app/src/main/res/values-fr/strings.xml | 1 +
app/src/main/res/values-hr/strings.xml | 1 +
app/src/main/res/values-hu/strings.xml | 1 +
app/src/main/res/values-id/strings.xml | 1 +
app/src/main/res/values-it/strings.xml | 1 +
app/src/main/res/values-kk/strings.xml | 1 +
app/src/main/res/values-ku/strings.xml | 1 +
app/src/main/res/values-ms/strings.xml | 1 +
app/src/main/res/values-nl/strings.xml | 1 +
app/src/main/res/values-pl/strings.xml | 1 +
app/src/main/res/values-pt-rBR/strings.xml | 1 +
app/src/main/res/values-ru/strings.xml | 1 +
app/src/main/res/values-sq/strings.xml | 1 +
app/src/main/res/values-sr/strings.xml | 1 +
app/src/main/res/values-sv/strings.xml | 1 +
app/src/main/res/values-th/strings.xml | 1 +
app/src/main/res/values-tr/strings.xml | 1 +
app/src/main/res/values-ug/strings.xml | 1 +
app/src/main/res/values-uk/strings.xml | 1 +
app/src/main/res/values-uz/strings.xml | 1 +
app/src/main/res/values-zh/strings.xml | 1 +
app/src/main/res/values/strings.xml | 2 ++
29 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/com/quran/labs/androidquran/util/ShareUtil.kt b/app/src/main/java/com/quran/labs/androidquran/util/ShareUtil.kt
index 06b8661605..7680397dcf 100644
--- a/app/src/main/java/com/quran/labs/androidquran/util/ShareUtil.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/util/ShareUtil.kt
@@ -137,6 +137,6 @@ class ShareUtil @Inject internal constructor(private val quranDisplayData: Quran
shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
shareIntent.type = "audio/mp3"
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- activity.startActivity(Intent.createChooser(shareIntent, "Share"))
+ activity.startActivity(Intent.createChooser(shareIntent, activity.getString(R.string.share_audio_file_title)))
}
}
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 73bc2d4901..5b205754d8 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -240,6 +240,7 @@
تم نسخ الآية
+ Share Audio File
تصنيف المرجعية
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index 9ec44ee78d..11195285fa 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -286,6 +286,7 @@
Âyet kopyalandı
+ Share Audio File
Sayfa işareti etiketi
diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml
index e338fbdf8b..0f34787ecd 100644
--- a/app/src/main/res/values-bs/strings.xml
+++ b/app/src/main/res/values-bs/strings.xml
@@ -305,6 +305,7 @@
Ajet kopiran
+ Share Audio File
Označi zabilješku
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index bab66e2b91..ae8670b660 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -311,6 +311,7 @@
Vers kopiert
+ Share Audio File
Ein Schlagwort zum Lesezeichen hinzufügen
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 137dbd1829..339b705e93 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -170,6 +170,7 @@
Versículo copiado
+ Share Audio File
Etiquetar Favorito
Borrar Etiqueta
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 3834c97f48..649ecb3d82 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -167,6 +167,7 @@
آیه کپی شد
+ Share Audio File
تعیین برچسب برای نشانک
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index b1172bd431..05d65e6d42 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -258,6 +258,7 @@
Aya copiée
+ Share Audio File
Étiqueter le favori
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 86a3318dcc..4a1fa3d20d 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -305,6 +305,7 @@
Ajet kopiran
+ Share Audio File
Označi zabilješku
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index ad25141740..228bcf8c5b 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -234,6 +234,7 @@
Ája kimásolva
+ Share Audio File
Könyvelző címkézése
diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml
index eda7bd57b3..23ac5275cf 100644
--- a/app/src/main/res/values-id/strings.xml
+++ b/app/src/main/res/values-id/strings.xml
@@ -290,6 +290,7 @@
Ayat telah tersalin
+ Share Audio File
Labeli Penanda
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index b7bee30bae..66b23401ea 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -123,6 +123,7 @@
Avviare la riproduzione da:
Inizio della pagina
Ayah Copiato
+ Share Audio File
Segnalibro
Cancellare il tag
Modifica tag
diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml
index 77500bfc18..f030f479ab 100644
--- a/app/src/main/res/values-kk/strings.xml
+++ b/app/src/main/res/values-kk/strings.xml
@@ -291,6 +291,7 @@
Аят көшірілді
+ Share Audio File
Бетбелгіні белгілеу
diff --git a/app/src/main/res/values-ku/strings.xml b/app/src/main/res/values-ku/strings.xml
index c34e4e6625..0eebc3becd 100644
--- a/app/src/main/res/values-ku/strings.xml
+++ b/app/src/main/res/values-ku/strings.xml
@@ -187,6 +187,7 @@
ئایەت لەبەریگیراوە
+ Share Audio File
بڕگە نیشانەکرا
diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml
index ebf680ce38..749c5f8da2 100644
--- a/app/src/main/res/values-ms/strings.xml
+++ b/app/src/main/res/values-ms/strings.xml
@@ -260,6 +260,7 @@
Ayat telah disalin
+ Share Audio File
Beri Label pada Penanda
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 6545623e91..b182bbc86c 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -282,6 +282,7 @@ Voor de vertaalde pagina\'s (/voor vertalingen): Ga naar instellingen en wijzig
Ayah Gekopieerd
+ Share Audio File
Label Bladwijzer
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 0945ece736..d2423a99b6 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -295,6 +295,7 @@
Ayah Skopiowany
+ Share Audio File
Tag Zakładka
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 286e311cad..3d9ddaf84d 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -205,6 +205,7 @@ escolher um leitor-qari diferente. Clique play para baixar e reproduzir a págin
Ayah Copiada
+ Share Audio File
Tagear Marcador
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 5af0673500..d3da9addd8 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -344,6 +344,7 @@
Аят скопирован
+ Share Audio File
Отметить закладку
diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-sq/strings.xml
index 51def7be8c..13d074e440 100644
--- a/app/src/main/res/values-sq/strings.xml
+++ b/app/src/main/res/values-sq/strings.xml
@@ -293,6 +293,7 @@
Ajahu kopjon
+ Share Audio File
Tag Libërshënues
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 05009fcd2a..d37de82a85 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -305,6 +305,7 @@
Ajet kopiran
+ Share Audio File
Označi zabelešku
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 43c1d06432..6e12fc4138 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -293,6 +293,7 @@
Ayah Kopierad
+ Share Audio File
Tag Bookmark
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index ee9dacdc34..a38f9f2193 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -284,6 +284,7 @@
อายะห์ คัดลอก
+ Share Audio File
ที่คั่นหน้าแท็ก
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 9ec44ee78d..11195285fa 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -286,6 +286,7 @@
Âyet kopyalandı
+ Share Audio File
Sayfa işareti etiketi
diff --git a/app/src/main/res/values-ug/strings.xml b/app/src/main/res/values-ug/strings.xml
index eb2bf74b2a..497a034ad0 100644
--- a/app/src/main/res/values-ug/strings.xml
+++ b/app/src/main/res/values-ug/strings.xml
@@ -160,6 +160,7 @@
ئايەت كۆچۈرۈلدى
+ Share Audio File
خەتكۈچكە بەلگە قوش
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 4de0b3641e..519ed660b8 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -296,6 +296,7 @@
Ая скопійований
+ Share Audio File
Закладка тега
diff --git a/app/src/main/res/values-uz/strings.xml b/app/src/main/res/values-uz/strings.xml
index f6a801f835..2187c221b0 100644
--- a/app/src/main/res/values-uz/strings.xml
+++ b/app/src/main/res/values-uz/strings.xml
@@ -319,6 +319,7 @@
Oyat xotiraga koʻchirildi
+ Share Audio File
Xatchoʻpni teglash
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 61d3a72da6..672ee8cf84 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -175,6 +175,7 @@
复制成功
+ Share Audio File
为收藏加标签
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index af568b9731..7aded67319 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -365,6 +365,7 @@
Ayah Copied
+ Share Audio File
Tag Bookmark
@@ -411,6 +412,7 @@
Download selection
Delete selection
+
Quran Audio files Update
Several Quran audio files have been updated. Quran has removed
From 4162f19436b4ae59bc69bf99f1cff55966225b71 Mon Sep 17 00:00:00 2001
From: Doozy
Date: Thu, 7 Jul 2022 15:12:00 +0300
Subject: [PATCH 07/18] [bugfix] - app was crushing when full surah is selected
---
.../quran/labs/androidquran/ui/PagerActivity.java | 12 +++++++++---
.../com/quran/labs/androidquran/util/AudioUtils.kt | 2 +-
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
index e925927471..6b4e7cb1e7 100644
--- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
+++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
@@ -1883,7 +1883,7 @@ public void shareAyahAudio(SuraAyah start, SuraAyah end) {
}
selectedQari = audioStatusBar.getAudioInfo();
- AudioPathInfo audioPathInfo = audioUtils.getLocalAudioPathInfo(this,selectedQari);
+ AudioPathInfo audioPathInfo = audioUtils.getLocalAudioPathInfo(selectedQari);
assert audioPathInfo != null;
if (audioPathInfo.getGaplessDatabase() != null) {
@@ -1939,8 +1939,14 @@ public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull ArrayList
Date: Thu, 7 Jul 2022 15:23:47 +0300
Subject: [PATCH 08/18] [bugfix] - app was crushing when full surah is selected
2
---
.../java/com/quran/labs/androidquran/ui/PagerActivity.java | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
index 6b4e7cb1e7..6e7483fd3c 100644
--- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
+++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
@@ -1940,10 +1940,11 @@ public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull ArrayList
Date: Thu, 7 Jul 2022 16:53:13 +0300
Subject: [PATCH 09/18] request user to download files if not present
---
.../presenter/audio/AudioPresenter.kt | 2 +-
.../labs/androidquran/ui/PagerActivity.java | 31 +++++++++++++------
.../labs/androidquran/util/AudioUtils.kt | 4 +--
3 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt b/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt
index 3f9736d8da..9bc73b3ec1 100644
--- a/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt
@@ -87,7 +87,7 @@ constructor(private val quranDisplayData: QuranDisplayData,
lastAudioRequest?.let { play(it) }
}
- private fun getDownloadIntent(context: Context, request: AudioRequest): Intent? {
+ fun getDownloadIntent(context: Context, request: AudioRequest): Intent? {
val qari = request.qari
val audioPathInfo = request.audioPathInfo
val path = audioPathInfo.localDirectory
diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
index 6e7483fd3c..8f9d48c3b9 100644
--- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
+++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
@@ -1866,6 +1866,7 @@ public void onError(@NonNull Throwable e) {
public void shareAyahAudio(SuraAyah start, SuraAyah end) {
audioCacheFilePaths.clear();
+
if (start == null || end == null) {
return;
}else {
@@ -1887,7 +1888,16 @@ public void shareAyahAudio(SuraAyah start, SuraAyah end) {
assert audioPathInfo != null;
if (audioPathInfo.getGaplessDatabase() != null) {
- createAndShareAudio(selectedStartSuraAyah,selectedEndSuraAyah,audioPathInfo);
+ if (!audioUtils.haveAllFiles(audioPathInfo.getUrlFormat(),audioPathInfo.getLocalDirectory(),selectedStartSuraAyah,selectedEndSuraAyah,true)){
+ AudioRequest audioRequest = new AudioRequest(
+ selectedStartSuraAyah, selectedEndSuraAyah, selectedQari, 0, 0, true, false, audioPathInfo);
+ Intent downloadIntent = audioPresenter.getDownloadIntent(this, audioRequest);
+ if (downloadIntent != null) {
+ handleRequiredDownload(downloadIntent);
+ }
+ }else{
+ createAndShareAudio(selectedStartSuraAyah,selectedEndSuraAyah,audioPathInfo);
+ }
}
}
@@ -1939,15 +1949,16 @@ public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull ArrayList
Date: Mon, 11 Jul 2022 13:47:16 +0300
Subject: [PATCH 10/18] clean up
---
.../labs/androidquran/ui/PagerActivity.java | 328 +++++++++++-------
.../labs/androidquran/util/AudioUtils.kt | 14 +-
2 files changed, 213 insertions(+), 129 deletions(-)
diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
index 8f9d48c3b9..bbe2a8ab35 100644
--- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
+++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
@@ -147,7 +147,6 @@
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
-import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.observers.DisposableObserver;
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
import io.reactivex.rxjava3.schedulers.Schedulers;
@@ -228,8 +227,10 @@ public class PagerActivity extends AppCompatActivity implements
private int defaultNavigationBarColor;
private boolean isSplitScreen = false;
- @Nullable private QuranAyahInfo lastSelectedTranslationAyah;
- @Nullable private LocalTranslation[] lastActivatedLocalTranslations;
+ @Nullable
+ private QuranAyahInfo lastSelectedTranslationAyah;
+ @Nullable
+ private LocalTranslation[] lastActivatedLocalTranslations;
private PagerActivityComponent pagerActivityComponent;
@@ -243,7 +244,7 @@ public class PagerActivity extends AppCompatActivity implements
@Inject ShareUtil shareUtil;
@Inject AudioUtils audioUtils;
@Inject QuranDisplayData quranDisplayData;
- @Inject QuranInfo quranInfo;
+ @Inject QuranInfo quranInfo;
@Inject QuranFileUtils quranFileUtils;
@Inject AudioPresenter audioPresenter;
@Inject QuranEventLogger quranEventLogger;
@@ -261,8 +262,9 @@ public class PagerActivity extends AppCompatActivity implements
private final PagerHandler handler = new PagerHandler(this);
- public static final File audioCacheDirectory= new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath() +
- File.separator +"quran_android_cache");
+ public static final File audioCacheDirectory = new File(
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath() +
+ File.separator + "quran_android_cache");
private ArrayList audioCacheFilePaths = new ArrayList<>();
private SuraAyah selectedStartSuraAyah = null;
private SuraAyah selectedEndSuraAyah = null;
@@ -305,12 +307,21 @@ public void onCreate(Bundle savedInstanceState) {
isSplitScreen = quranSettings.isQuranSplitWithTranslation();
audioEventPresenterBridge = new AudioEventPresenterBridge(
audioEventPresenter,
- suraAyah -> { onAudioPlaybackAyahChanged(suraAyah); return null; }
+ suraAyah -> {
+ onAudioPlaybackAyahChanged(suraAyah);
+ return null;
+ }
);
readingEventPresenterBridge = new ReadingEventPresenterBridge(
readingEventPresenter,
- () -> { onPageClicked(); return null; },
- ayahSelection -> { onAyahSelectionChanged(ayahSelection); return null; }
+ () -> {
+ onPageClicked();
+ return null;
+ },
+ ayahSelection -> {
+ onAyahSelectionChanged(ayahSelection);
+ return null;
+ }
);
// remove the window background to avoid overdraw. note that, per Romain's blog, this is
@@ -356,7 +367,7 @@ public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.quran_page_activity_slider);
if (!audioCacheDirectory.exists()) {
- if (!audioCacheDirectory.mkdirs()){
+ if (!audioCacheDirectory.mkdirs()) {
Toast.makeText(PagerActivity.this, "could not create directory", Toast.LENGTH_SHORT).show();
}
}
@@ -443,7 +454,8 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse
} else if (position == barPos - 1 || position == barPos + 1) {
// Swiping to previous or next ViewPager page (i.e. next or previous quran page)
final SelectionIndicator updatedSelectionIndicator =
- SelectionIndicatorKt.withXScroll(selectionIndicator, viewPager.getWidth() - positionOffsetPixels);
+ SelectionIndicatorKt.withXScroll(selectionIndicator,
+ viewPager.getWidth() - positionOffsetPixels);
readingEventPresenterBridge.withSelectionIndicator(updatedSelectionIndicator);
} else {
readingEventPresenterBridge.clearSelectedAyah();
@@ -563,8 +575,14 @@ public void onPageSelected(int position) {
this::getCurrentPage,
() -> audioStatusBar,
() -> ayahToolBar,
- ayah -> { ensurePage(ayah.sura, ayah.ayah); return null; },
- sliderPage -> { showSlider(slidingPagerAdapter.getPagePosition(sliderPage)); return null; }
+ ayah -> {
+ ensurePage(ayah.sura, ayah.ayah);
+ return null;
+ },
+ sliderPage -> {
+ showSlider(slidingPagerAdapter.getPagePosition(sliderPage));
+ return null;
+ }
));
}
@@ -1393,16 +1411,16 @@ private void ensurePage(int sura, int ayah) {
private void requestTranslationsList() {
compositeDisposable.add(
Single.fromCallable(() ->
- translationsDBAdapter.getTranslations())
+ translationsDBAdapter.getTranslations())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver
>() {
@Override
public void onSuccess(@NonNull List translationList) {
final List sortedTranslations = new ArrayList<>(translationList);
- Collections.sort(sortedTranslations, new LocalTranslationDisplaySort());
+ Collections.sort(sortedTranslations, new LocalTranslationDisplaySort());
- int items = sortedTranslations.size();
+ int items = sortedTranslations.size();
String[] titles = new String[items];
for (int i = 0; i < items; i++) {
LocalTranslation item = sortedTranslations.get(i);
@@ -1419,7 +1437,8 @@ public void onSuccess(@NonNull List translationList) {
if (currentActiveTranslationsFilesNames.isEmpty() && items > 0) {
currentActiveTranslationsFilesNames = new HashSet<>();
for (int i = 0; i < items; i++) {
- currentActiveTranslationsFilesNames.add(sortedTranslations.get(i).getFilename());
+ currentActiveTranslationsFilesNames.add(
+ sortedTranslations.get(i).getFilename());
}
}
activeTranslationsFilesNames = currentActiveTranslationsFilesNames;
@@ -1452,7 +1471,8 @@ public void onSuccess(@NonNull Boolean isBookmarked) {
if (sura == null || ayah == null) {
// page bookmark
bookmarksCache.put(page, isBookmarked);
- bookmarksMenuItem.setIcon(isBookmarked ? com.quran.labs.androidquran.common.toolbar.R.drawable.ic_favorite : com.quran.labs.androidquran.common.toolbar.R.drawable.ic_not_favorite);
+ bookmarksMenuItem.setIcon(
+ isBookmarked ? com.quran.labs.androidquran.common.toolbar.R.drawable.ic_favorite : com.quran.labs.androidquran.common.toolbar.R.drawable.ic_not_favorite);
} else {
// ayah bookmark
SuraAyah suraAyah = new SuraAyah(sura, ayah);
@@ -1501,7 +1521,8 @@ private void refreshBookmarksMenu() {
bookmarked = bookmarksCache.get(page - 1);
}
- menuItem.setIcon(bookmarked ? com.quran.labs.androidquran.common.toolbar.R.drawable.ic_favorite : com.quran.labs.androidquran.common.toolbar.R.drawable.ic_not_favorite);
+ menuItem.setIcon(
+ bookmarked ? com.quran.labs.androidquran.common.toolbar.R.drawable.ic_favorite : com.quran.labs.androidquran.common.toolbar.R.drawable.ic_not_favorite);
} else {
supportInvalidateOptionsMenu();
}
@@ -1542,7 +1563,7 @@ private void playFromAyah(int page, int startSura, int startAyah) {
final SuraAyah start = new SuraAyah(startSura, startAyah);
final SuraAyah end = getSelectionEnd();
// handle the case of multiple ayat being selected and play them as a range if so
- final SuraAyah ending = (end == null || start.equals(end) || start.after(end))? null : end;
+ final SuraAyah ending = (end == null || start.equals(end) || start.after(end)) ? null : end;
playFromAyah(start, ending, page, 0, 0, ending != null);
}
@@ -1785,7 +1806,7 @@ public boolean onMenuItemClick(MenuItem item) {
shareAyahLink(startSuraAyah, endSuraAyah);
} else if (itemId == com.quran.labs.androidquran.common.toolbar.R.id.cab_share_ayah_text) {
shareAyah(startSuraAyah, endSuraAyah, false);
- }else if (itemId == com.quran.labs.androidquran.common.toolbar.R.id.cab_share_ayah_audio) {
+ } else if (itemId == com.quran.labs.androidquran.common.toolbar.R.id.cab_share_ayah_audio) {
shareAyahAudio(startSuraAyah, endSuraAyah);
} else if (itemId == com.quran.labs.androidquran.common.toolbar.R.id.cab_copy_ayah) {
shareAyah(startSuraAyah, endSuraAyah, true);
@@ -1823,7 +1844,8 @@ private void shareAyah(SuraAyah start, SuraAyah end, final boolean isCopy) {
if (isCopy) {
shareUtil.copyToClipboard(this, shareText);
} else {
- shareUtil.shareViaIntent(this, shareText, com.quran.labs.androidquran.common.toolbar.R.string.share_ayah_text);
+ shareUtil.shareViaIntent(this, shareText,
+ com.quran.labs.androidquran.common.toolbar.R.string.share_ayah_text);
}
}
@@ -1852,7 +1874,8 @@ public void shareAyahLink(SuraAyah start, SuraAyah end) {
.subscribeWith(new DisposableSingleObserver() {
@Override
public void onSuccess(@NonNull String url) {
- shareUtil.shareViaIntent(PagerActivity.this, url, com.quran.labs.androidquran.common.toolbar.R.string.share_ayah);
+ shareUtil.shareViaIntent(PagerActivity.this, url,
+ com.quran.labs.androidquran.common.toolbar.R.string.share_ayah);
dismissProgressDialog();
}
@@ -1867,128 +1890,117 @@ public void onError(@NonNull Throwable e) {
public void shareAyahAudio(SuraAyah start, SuraAyah end) {
audioCacheFilePaths.clear();
- if (start == null || end == null) {
- return;
- }else {
- kotlin.Pair pair;
- if (start.compareTo(end) <= 0) {
- pair = TuplesKt.to(start, end);
- } else {
- Timber.Forest.e(new IllegalStateException("End isn't larger than the start: " + start + " to " + end));
- pair = TuplesKt.to(end, start);
- }
-
- kotlin.Pair pair2 = pair;
- selectedStartSuraAyah = (SuraAyah) pair2.component1();
- selectedEndSuraAyah = (SuraAyah) pair2.component2();
- }
+ kotlin.Pair pair2 = getReorderedAyatPair(start, end);
+ selectedStartSuraAyah = (SuraAyah) pair2.component1();
+ selectedEndSuraAyah = (SuraAyah) pair2.component2();
- selectedQari = audioStatusBar.getAudioInfo();
- AudioPathInfo audioPathInfo = audioUtils.getLocalAudioPathInfo(selectedQari);
+ selectedQari = audioStatusBar.getAudioInfo();
+ AudioPathInfo audioPathInfo = audioUtils.getLocalAudioPathInfo(selectedQari);
assert audioPathInfo != null;
- if (audioPathInfo.getGaplessDatabase() != null) {
- if (!audioUtils.haveAllFiles(audioPathInfo.getUrlFormat(),audioPathInfo.getLocalDirectory(),selectedStartSuraAyah,selectedEndSuraAyah,true)){
- AudioRequest audioRequest = new AudioRequest(
- selectedStartSuraAyah, selectedEndSuraAyah, selectedQari, 0, 0, true, false, audioPathInfo);
- Intent downloadIntent = audioPresenter.getDownloadIntent(this, audioRequest);
- if (downloadIntent != null) {
- handleRequiredDownload(downloadIntent);
- }
- }else{
- createAndShareAudio(selectedStartSuraAyah,selectedEndSuraAyah,audioPathInfo);
+ boolean gaplessDatabaseExists = audioPathInfo.getGaplessDatabase() != null;
+
+ if (gaplessDatabaseExists) {
+ if (audioFilesExist(audioPathInfo)) {
+ createAndShareAudio(selectedStartSuraAyah, selectedEndSuraAyah, audioPathInfo);
+ } else {
+ requestDownload(audioPathInfo);
}
}
}
+ private kotlin.Pair getReorderedAyatPair(SuraAyah start, SuraAyah end) {
+ kotlin.Pair pair;
+ if (start.compareTo(end) <= 0) {
+ pair = TuplesKt.to(start, end);
+ } else {
+ Timber.Forest.e(
+ new IllegalStateException("End isn't larger than the start: " + start + " to " + end));
+ pair = TuplesKt.to(end, start);
+ }
+ return pair;
+ }
+
+ private boolean audioFilesExist(AudioPathInfo audioPathInfo) {
+ return audioUtils.haveAllFiles(audioPathInfo.getUrlFormat(), audioPathInfo.getLocalDirectory(),
+ selectedStartSuraAyah, selectedEndSuraAyah, true);
+ }
+
private void createAndShareAudio(SuraAyah start, SuraAyah end, AudioPathInfo audioPathInfo) {
showProgressDialog();
- String databasePath = audioPathInfo.getGaplessDatabase();
compositeDisposable.add(
- Single.fromCallable(() -> {
- assert databasePath != null;
- SuraTimingDatabaseHandler db = SuraTimingDatabaseHandler.Companion.getDatabaseHandler(databasePath);
- SparseIntArray firstSurahMap = new SparseIntArray();
- SparseIntArray lastSurahMap = new SparseIntArray();
- Cursor firstSurahCursor;
- Cursor lastSurahCursor = null;
-
- try {
- firstSurahCursor = db.getAyahTimings(start.sura);
- Timber.Forest.d("got cursor of data");
- if (firstSurahCursor != null && firstSurahCursor.moveToFirst()) {
- do {
- int ayah = firstSurahCursor.getInt(1);
- int time = firstSurahCursor.getInt(2);
- firstSurahMap.put(ayah, time);
- } while (firstSurahCursor.moveToNext());
- }
-
- lastSurahCursor = db.getAyahTimings(end.sura);
- Timber.Forest.d("got cursor of data");
- if (lastSurahCursor != null && lastSurahCursor.moveToFirst()) {
- do {
- int ayah = lastSurahCursor.getInt(1);
- int time = lastSurahCursor.getInt(2);
- lastSurahMap.put(ayah, time);
- } while (lastSurahCursor.moveToNext());
- }
- } catch (SQLException sqlException) {
- Timber.Forest.e(sqlException);
- } finally {
- closeCursor(lastSurahCursor);
- }
- ArrayList mapArray = new ArrayList<>(Arrays.asList(firstSurahMap, lastSurahMap));
- return mapArray;
- }).subscribeOn(Schedulers.io())
+ Single.fromCallable(() -> getTimingData(start, end, audioPathInfo))
+ .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver>() {
@Override
- public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull ArrayList sparseIntArrayList) {
+ public void onSuccess(
+ @io.reactivex.rxjava3.annotations.NonNull ArrayList sparseIntArrayList) {
Intrinsics.checkNotNullExpressionValue(sparseIntArrayList, "mapArray");
int startAyah = start.ayah;
int endAyah = end.ayah;
- int startAyahTime = 0;
- int endAyahTime = sparseIntArrayList.get(1).get(endAyah+1);
-
- if (startAyah!=1){
- startAyahTime = sparseIntArrayList.get(0).get(startAyah);
+ SparseIntArray startSurahTimingDataArray = sparseIntArrayList.get(0);
+ SparseIntArray endSurahTimingDataArray = sparseIntArrayList.get(1);
+ int startAyahTime;
+ int endAyahTime;
+
+ boolean isFirstAyahInSurah = startAyah == 1;
+ int startTimeOfAyahAfterEndAyah = endSurahTimingDataArray.get(endAyah + 1);
+ boolean isLastAyahInSurah = startTimeOfAyahAfterEndAyah == 0;
+
+ if (isFirstAyahInSurah) {
+ startAyahTime = 0;
+ } else {
+ startAyahTime = startSurahTimingDataArray.get(startAyah);
}
- if (endAyahTime == 0){
- endAyahTime = audioUtils.getSurahDuration(PagerActivity.this,audioUtils.getSurahAudioPath(audioPathInfo,end.sura));
+
+ if (isLastAyahInSurah) {
+ endAyahTime = audioUtils.getSurahDuration(PagerActivity.this,
+ audioUtils.getSurahAudioPath(audioPathInfo, end.sura));
+ } else {
+ endAyahTime = startTimeOfAyahAfterEndAyah;
}
+ boolean startAndEndAyahAreInSameSurah = start.sura == end.sura;
- if (start.sura == end.sura){
- String audioSegmentPath = audioUtils.getSurahSegment(audioUtils.getSurahAudioPath(audioPathInfo,start.sura),startAyahTime,endAyahTime);
+ if (startAndEndAyahAreInSameSurah) {
+ String audioSegmentPath = audioUtils.getSurahSegment(
+ audioUtils.getSurahAudioPath(audioPathInfo, start.sura), startAyahTime,
+ endAyahTime);
audioCacheFilePaths.add(audioSegmentPath);
- renameSharableAudioFile(audioSegmentPath);
- shareUtil.shareAudioFileIntent(PagerActivity.this,new File(audioSegmentPath));
- }else {
- ArrayList paths = new ArrayList<>();
- String path1 = audioUtils.getSurahAudioPath(audioPathInfo,start.sura);
- int upperCut = audioUtils.getSurahDuration(PagerActivity.this,path1);
- String firstSegment = audioUtils.getSurahSegment(path1,startAyahTime,upperCut);
- String path2 = audioUtils.getSurahAudioPath(audioPathInfo,end.sura);
- String lastSegment = audioUtils.getSurahSegment(path2,0,endAyahTime);
-
- for (int surahIndex = start.sura; surahIndex<=end.sura; surahIndex++){
- if (surahIndex == start.sura){
- paths.add(firstSegment);
+ shareAudioSegment(renameSharableAudioFile(audioSegmentPath));
+ } else {
+ ArrayList segmentPaths = new ArrayList<>();
+ int endOfSurah = -1;
+ int startOfSurah = 0;
+ String startSegmentPath = getSurahSegmentPath(audioPathInfo, start.sura,
+ startAyahTime, endOfSurah);
+ String lastSegmentPath = getSurahSegmentPath(audioPathInfo, end.sura,
+ startOfSurah, endAyahTime);
+
+ for (int surahIndex = start.sura; surahIndex <= end.sura; surahIndex++) {
+ boolean isTheFirstSurah = surahIndex == start.sura;
+ boolean isMiddleSurah = (surahIndex != start.sura) && (surahIndex != end.sura);
+
+ if (isTheFirstSurah) {
+ segmentPaths.add(startSegmentPath);
continue;
}
- if (surahIndex != end.sura){
- paths.add(audioUtils.getSurahAudioPath(audioPathInfo,surahIndex));
+ if (isMiddleSurah) {
+ segmentPaths.add(audioUtils.getSurahAudioPath(audioPathInfo, surahIndex));
continue;
}
- paths.add(lastSegment);
+ segmentPaths.add(lastSegmentPath);
}
- if (!paths.isEmpty()){
- audioCacheFilePaths.addAll(paths);
- File sharableAudioFile = audioUtils.getMergedAudioFromSegments(paths);
- renameSharableAudioFile(sharableAudioFile.getPath());
- shareUtil.shareAudioFileIntent(PagerActivity.this,sharableAudioFile);
+
+ boolean audioSegmentsWereCreated = !segmentPaths.isEmpty();
+
+ if (audioSegmentsWereCreated) {
+ audioCacheFilePaths.addAll(segmentPaths);
+ String sharableAudioFilePath = audioUtils.getMergedAudioFromSegments(
+ segmentPaths);
+ shareAudioSegment(renameSharableAudioFile(sharableAudioFilePath));
}
}
dismissProgressDialog();
@@ -1997,21 +2009,93 @@ public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull ArrayList getTimingData(SuraAyah start, SuraAyah end,
+ AudioPathInfo audioPathInfo) {
+ String databasePath = audioPathInfo.getGaplessDatabase();
+
+ assert databasePath != null;
+ SuraTimingDatabaseHandler db = SuraTimingDatabaseHandler.Companion.getDatabaseHandler(
+ databasePath);
+ SparseIntArray firstSurahMap = new SparseIntArray();
+ SparseIntArray lastSurahMap = new SparseIntArray();
+ Cursor firstSurahCursor = null;
+ Cursor lastSurahCursor = null;
+
+ try {
+ firstSurahCursor = db.getAyahTimings(start.sura);
+ firstSurahMap = populateArrayFromCursor(firstSurahCursor);
+
+ lastSurahCursor = db.getAyahTimings(end.sura);
+ lastSurahMap = populateArrayFromCursor(lastSurahCursor);
+
+ } catch (SQLException sqlException) {
+ Timber.Forest.e(sqlException);
+ } finally {
+ closeCursor(firstSurahCursor);
+ closeCursor(lastSurahCursor);
+ }
+
+ ArrayList mapArray = new ArrayList<>(
+ Arrays.asList(firstSurahMap, lastSurahMap));
+ return mapArray;
+ }
+
+ private SparseIntArray populateArrayFromCursor(Cursor cursor) {
+ SparseIntArray sparseIntArray = new SparseIntArray();
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ int ayah = cursor.getInt(1);
+ int time = cursor.getInt(2);
+ sparseIntArray.put(ayah, time);
+ } while (cursor.moveToNext());
+ }
+ return sparseIntArray;
+ }
+
+ private String renameSharableAudioFile(String audioSegmentPath) {
String newAudioFileName = selectedQari.getPath() + "_" + selectedStartSuraAyah.sura + "-" + selectedStartSuraAyah.ayah + "_" + selectedEndSuraAyah.sura + "-" + selectedEndSuraAyah.ayah;
- String newAudioFilePath = audioCacheDirectory + File.separator+ newAudioFileName + ".mp3";
+ String newAudioFilePath = audioCacheDirectory + File.separator + newAudioFileName + ".mp3";
new File(audioSegmentPath).renameTo(new File(newAudioFilePath));
audioCacheFilePaths.remove(audioSegmentPath);
- for (String path : audioCacheFilePaths){
+ for (String path : audioCacheFilePaths) {
new File(path).delete();
}
audioCacheFilePaths.clear();
+ return newAudioFilePath;
+ }
+
+ private void shareAudioSegment(String path) {
+ shareUtil.shareAudioFileIntent(PagerActivity.this, new File(path));
+ }
+
+ private String getSurahSegmentPath(AudioPathInfo audioPathInfo, int surah,
+ int startAyahTime, int endAyahTime) {
+ int lowerBoundTime = startAyahTime;
+ int upperBoundTime = endAyahTime;
+
+ String audioFilePath = audioUtils.getSurahAudioPath(audioPathInfo, surah);
+ boolean isFirstSegment = endAyahTime < 0;
+
+ if (isFirstSegment) {
+ upperBoundTime = audioUtils.getSurahDuration(PagerActivity.this, audioFilePath);
+ }
+
+ return audioUtils.getSurahSegment(audioFilePath, lowerBoundTime, upperBoundTime);
+ }
+
+ private void requestDownload(AudioPathInfo audioPathInfo) {
+ AudioRequest audioRequest = new AudioRequest(
+ selectedStartSuraAyah, selectedEndSuraAyah, selectedQari, 0, 0, true, false, audioPathInfo);
+
+ Intent downloadIntent = audioPresenter.getDownloadIntent(this, audioRequest);
+ if (downloadIntent != null) {
+ handleRequiredDownload(downloadIntent);
+ }
}
private void showProgressDialog() {
diff --git a/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt b/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
index e41e4e364f..b9bd9f6be8 100644
--- a/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
@@ -306,14 +306,14 @@ class AudioUtils @Inject constructor(
return null
}
- fun getMergedAudioFromSegments(segments: ArrayList): File {
+ fun getMergedAudioFromSegments(segments: ArrayList): String {
var mergedAudioPath = segments[0]
if (segments.size > 1) {
for (i in 1 until segments.size) {
mergedAudioPath = mergeAudios(mergedAudioPath, segments[i])!!
}
}
- return File(mergedAudioPath)
+ return mergedAudioPath
}
private fun mergeAudios(path1: String, path2: String): String? {
@@ -353,26 +353,26 @@ class AudioUtils @Inject constructor(
}
fun getSurahSegment(path: String, lowerCut: Int, upperCut: Int): String? {
+ if (lowerCut == 0 && upperCut == 0) {
+ return null
+ }
val tempAudioName = UUID.randomUUID().toString() + ".mp3"
val destFile = File(PagerActivity.audioCacheDirectory.path + File.separator + tempAudioName)
val mSoundFile = arrayOfNulls(1)
try {
mSoundFile[0] = CheapSoundFile.create(path, null)
- if (lowerCut == 0 && upperCut == 0) {
- return null
- }
val startTime = lowerCut.toFloat() / 1000
val endTime = upperCut.toFloat() / 1000
val samplesPerFrame = mSoundFile[0]?.samplesPerFrame
val sampleRate = mSoundFile[0]?.sampleRate
val avg = sampleRate?.div(samplesPerFrame!!)
val startFrames = (startTime * avg!!).roundToInt()
- val endFrames = (endTime * avg!!).roundToInt()
+ val endFrames = (endTime * avg).roundToInt()
mSoundFile[0]?.WriteFile(destFile, startFrames, endFrames - startFrames)
} catch (e: IOException) {
e.printStackTrace()
}
- return destFile.path
+ return destFile.absolutePath
}
fun getSurahDuration(context: Context,path: String): Int {
From cf84a9571ec6dd647ee252f5b2d3204100e99870 Mon Sep 17 00:00:00 2001
From: Doozy
Date: Sat, 30 Jul 2022 11:21:24 +0300
Subject: [PATCH 11/18] transffered to audio feature
---
.../labs/androidquran/ui/PagerActivity.java | 165 +----------
.../labs/androidquran/util/AudioUtils.kt | 91 +-----
feature/audio/build.gradle | 3 +
.../database/SuraTimingDatabaseHandler.kt | 126 +++++++++
.../feature/audio/util/AudioShareUtils.kt | 259 ++++++++++++++++++
.../audio/util/soundfile}/CheapMP3.java | 2 +-
.../audio/util/soundfile}/CheapSoundFile.java | 5 +-
7 files changed, 400 insertions(+), 251 deletions(-)
create mode 100644 feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/database/SuraTimingDatabaseHandler.kt
create mode 100644 feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
rename {app/src/main/java/com/quran/labs/androidquran/util/audioConversionUtils => feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile}/CheapMP3.java (99%)
rename {app/src/main/java/com/quran/labs/androidquran/util/audioConversionUtils => feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile}/CheapSoundFile.java (97%)
diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
index bbe2a8ab35..891ab2c90d 100644
--- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
+++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
@@ -85,6 +85,7 @@
import com.quran.labs.androidquran.di.component.activity.PagerActivityComponent;
import com.quran.labs.androidquran.di.module.activity.PagerActivityModule;
import com.quran.labs.androidquran.di.module.fragment.QuranPageModule;
+import com.quran.labs.androidquran.feature.audio.util.AudioShareUtils;
import com.quran.labs.androidquran.model.bookmark.BookmarkModel;
import com.quran.labs.androidquran.model.translation.ArabicDatabaseUtils;
import com.quran.labs.androidquran.presenter.audio.AudioPresenter;
@@ -1902,7 +1903,11 @@ public void shareAyahAudio(SuraAyah start, SuraAyah end) {
if (gaplessDatabaseExists) {
if (audioFilesExist(audioPathInfo)) {
- createAndShareAudio(selectedStartSuraAyah, selectedEndSuraAyah, audioPathInfo);
+ AudioShareUtils audioShareUtils = new AudioShareUtils();
+ String path = audioShareUtils.createSharableAudioFile(this, selectedStartSuraAyah,
+ selectedEndSuraAyah, selectedQari, audioPathInfo.getUrlFormat(),
+ audioPathInfo.getGaplessDatabase());
+ shareAudioSegment(path);
} else {
requestDownload(audioPathInfo);
}
@@ -1926,168 +1931,10 @@ private boolean audioFilesExist(AudioPathInfo audioPathInfo) {
selectedStartSuraAyah, selectedEndSuraAyah, true);
}
- private void createAndShareAudio(SuraAyah start, SuraAyah end, AudioPathInfo audioPathInfo) {
- showProgressDialog();
- compositeDisposable.add(
- Single.fromCallable(() -> getTimingData(start, end, audioPathInfo))
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribeWith(new DisposableSingleObserver>() {
- @Override
- public void onSuccess(
- @io.reactivex.rxjava3.annotations.NonNull ArrayList sparseIntArrayList) {
- Intrinsics.checkNotNullExpressionValue(sparseIntArrayList, "mapArray");
-
- int startAyah = start.ayah;
- int endAyah = end.ayah;
- SparseIntArray startSurahTimingDataArray = sparseIntArrayList.get(0);
- SparseIntArray endSurahTimingDataArray = sparseIntArrayList.get(1);
- int startAyahTime;
- int endAyahTime;
-
- boolean isFirstAyahInSurah = startAyah == 1;
- int startTimeOfAyahAfterEndAyah = endSurahTimingDataArray.get(endAyah + 1);
- boolean isLastAyahInSurah = startTimeOfAyahAfterEndAyah == 0;
-
- if (isFirstAyahInSurah) {
- startAyahTime = 0;
- } else {
- startAyahTime = startSurahTimingDataArray.get(startAyah);
- }
-
- if (isLastAyahInSurah) {
- endAyahTime = audioUtils.getSurahDuration(PagerActivity.this,
- audioUtils.getSurahAudioPath(audioPathInfo, end.sura));
- } else {
- endAyahTime = startTimeOfAyahAfterEndAyah;
- }
-
- boolean startAndEndAyahAreInSameSurah = start.sura == end.sura;
-
- if (startAndEndAyahAreInSameSurah) {
- String audioSegmentPath = audioUtils.getSurahSegment(
- audioUtils.getSurahAudioPath(audioPathInfo, start.sura), startAyahTime,
- endAyahTime);
- audioCacheFilePaths.add(audioSegmentPath);
- shareAudioSegment(renameSharableAudioFile(audioSegmentPath));
- } else {
- ArrayList segmentPaths = new ArrayList<>();
- int endOfSurah = -1;
- int startOfSurah = 0;
- String startSegmentPath = getSurahSegmentPath(audioPathInfo, start.sura,
- startAyahTime, endOfSurah);
- String lastSegmentPath = getSurahSegmentPath(audioPathInfo, end.sura,
- startOfSurah, endAyahTime);
-
- for (int surahIndex = start.sura; surahIndex <= end.sura; surahIndex++) {
- boolean isTheFirstSurah = surahIndex == start.sura;
- boolean isMiddleSurah = (surahIndex != start.sura) && (surahIndex != end.sura);
-
- if (isTheFirstSurah) {
- segmentPaths.add(startSegmentPath);
- continue;
- }
- if (isMiddleSurah) {
- segmentPaths.add(audioUtils.getSurahAudioPath(audioPathInfo, surahIndex));
- continue;
- }
- segmentPaths.add(lastSegmentPath);
- }
-
- boolean audioSegmentsWereCreated = !segmentPaths.isEmpty();
-
- if (audioSegmentsWereCreated) {
- audioCacheFilePaths.addAll(segmentPaths);
- String sharableAudioFilePath = audioUtils.getMergedAudioFromSegments(
- segmentPaths);
- shareAudioSegment(renameSharableAudioFile(sharableAudioFilePath));
- }
- }
- dismissProgressDialog();
- }
-
- @Override
- public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
- dismissProgressDialog();
- }
- })
- );
- }
-
- private ArrayList getTimingData(SuraAyah start, SuraAyah end,
- AudioPathInfo audioPathInfo) {
- String databasePath = audioPathInfo.getGaplessDatabase();
-
- assert databasePath != null;
- SuraTimingDatabaseHandler db = SuraTimingDatabaseHandler.Companion.getDatabaseHandler(
- databasePath);
- SparseIntArray firstSurahMap = new SparseIntArray();
- SparseIntArray lastSurahMap = new SparseIntArray();
- Cursor firstSurahCursor = null;
- Cursor lastSurahCursor = null;
-
- try {
- firstSurahCursor = db.getAyahTimings(start.sura);
- firstSurahMap = populateArrayFromCursor(firstSurahCursor);
-
- lastSurahCursor = db.getAyahTimings(end.sura);
- lastSurahMap = populateArrayFromCursor(lastSurahCursor);
-
- } catch (SQLException sqlException) {
- Timber.Forest.e(sqlException);
- } finally {
- closeCursor(firstSurahCursor);
- closeCursor(lastSurahCursor);
- }
-
- ArrayList mapArray = new ArrayList<>(
- Arrays.asList(firstSurahMap, lastSurahMap));
- return mapArray;
- }
-
- private SparseIntArray populateArrayFromCursor(Cursor cursor) {
- SparseIntArray sparseIntArray = new SparseIntArray();
- if (cursor != null && cursor.moveToFirst()) {
- do {
- int ayah = cursor.getInt(1);
- int time = cursor.getInt(2);
- sparseIntArray.put(ayah, time);
- } while (cursor.moveToNext());
- }
- return sparseIntArray;
- }
-
- private String renameSharableAudioFile(String audioSegmentPath) {
- String newAudioFileName = selectedQari.getPath() + "_" + selectedStartSuraAyah.sura + "-" + selectedStartSuraAyah.ayah + "_" + selectedEndSuraAyah.sura + "-" + selectedEndSuraAyah.ayah;
- String newAudioFilePath = audioCacheDirectory + File.separator + newAudioFileName + ".mp3";
- new File(audioSegmentPath).renameTo(new File(newAudioFilePath));
- audioCacheFilePaths.remove(audioSegmentPath);
- for (String path : audioCacheFilePaths) {
- new File(path).delete();
- }
- audioCacheFilePaths.clear();
- return newAudioFilePath;
- }
-
private void shareAudioSegment(String path) {
shareUtil.shareAudioFileIntent(PagerActivity.this, new File(path));
}
- private String getSurahSegmentPath(AudioPathInfo audioPathInfo, int surah,
- int startAyahTime, int endAyahTime) {
- int lowerBoundTime = startAyahTime;
- int upperBoundTime = endAyahTime;
-
- String audioFilePath = audioUtils.getSurahAudioPath(audioPathInfo, surah);
- boolean isFirstSegment = endAyahTime < 0;
-
- if (isFirstSegment) {
- upperBoundTime = audioUtils.getSurahDuration(PagerActivity.this, audioFilePath);
- }
-
- return audioUtils.getSurahSegment(audioFilePath, lowerBoundTime, upperBoundTime);
- }
-
private void requestDownload(AudioPathInfo audioPathInfo) {
AudioRequest audioRequest = new AudioRequest(
selectedStartSuraAyah, selectedEndSuraAyah, selectedQari, 0, 0, true, false, audioPathInfo);
diff --git a/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt b/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
index b9bd9f6be8..4df8b728cf 100644
--- a/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
@@ -2,11 +2,7 @@ package com.quran.labs.androidquran.util
import android.content.Context
import android.content.Intent
-import android.media.MediaMetadataRetriever
-import android.net.Uri
import androidx.annotation.VisibleForTesting
-import androidx.core.content.ContextCompat.startActivity
-import androidx.core.content.FileProvider
import com.quran.data.core.QuranInfo
import com.quran.data.model.SuraAyah
import com.quran.labs.androidquran.common.audio.model.AudioConfiguration
@@ -14,13 +10,10 @@ import com.quran.labs.androidquran.common.audio.model.QariItem
import com.quran.labs.androidquran.common.audio.util.QariUtil
import com.quran.labs.androidquran.dao.audio.AudioPathInfo
import com.quran.labs.androidquran.service.AudioService
-import com.quran.labs.androidquran.ui.PagerActivity
-import com.quran.labs.androidquran.util.audioConversionUtils.CheapSoundFile
import timber.log.Timber
-import java.io.*
-import java.util.*
+import java.io.File
+import java.util.Locale
import javax.inject.Inject
-import kotlin.math.roundToInt
class AudioUtils @Inject constructor(
private val quranInfo: QuranInfo,
@@ -306,86 +299,6 @@ class AudioUtils @Inject constructor(
return null
}
- fun getMergedAudioFromSegments(segments: ArrayList): String {
- var mergedAudioPath = segments[0]
- if (segments.size > 1) {
- for (i in 1 until segments.size) {
- mergedAudioPath = mergeAudios(mergedAudioPath, segments[i])!!
- }
- }
- return mergedAudioPath
- }
-
- private fun mergeAudios(path1: String, path2: String): String? {
- val tempAudioName = UUID.randomUUID().toString() + ".mp3"
- val destFile = File(PagerActivity.audioCacheDirectory.path + File.separator + tempAudioName)
- try {
- val fileInputStream = FileInputStream(path1)
- val bArr = ByteArray(1048576)
- val fileOutputStream = FileOutputStream(destFile)
- while (true) {
- val read = fileInputStream.read(bArr)
- if (read == -1) {
- break
- }
- fileOutputStream.write(bArr, 0, read)
- fileOutputStream.flush()
- }
- fileInputStream.close()
- val fileInputStream2 = FileInputStream(path2)
- while (true) {
- val read2 = fileInputStream2.read(bArr)
- if (read2 == -1) {
- break
- }
- fileOutputStream.write(bArr, 0, read2)
- fileOutputStream.flush()
- }
- fileInputStream2.close()
- fileOutputStream.close()
- return destFile.path
- } catch (e2: FileNotFoundException) {
- e2.printStackTrace()
- } catch (e: IOException) {
- e.printStackTrace()
- }
- return null
- }
-
- fun getSurahSegment(path: String, lowerCut: Int, upperCut: Int): String? {
- if (lowerCut == 0 && upperCut == 0) {
- return null
- }
- val tempAudioName = UUID.randomUUID().toString() + ".mp3"
- val destFile = File(PagerActivity.audioCacheDirectory.path + File.separator + tempAudioName)
- val mSoundFile = arrayOfNulls(1)
- try {
- mSoundFile[0] = CheapSoundFile.create(path, null)
- val startTime = lowerCut.toFloat() / 1000
- val endTime = upperCut.toFloat() / 1000
- val samplesPerFrame = mSoundFile[0]?.samplesPerFrame
- val sampleRate = mSoundFile[0]?.sampleRate
- val avg = sampleRate?.div(samplesPerFrame!!)
- val startFrames = (startTime * avg!!).roundToInt()
- val endFrames = (endTime * avg).roundToInt()
- mSoundFile[0]?.WriteFile(destFile, startFrames, endFrames - startFrames)
- } catch (e: IOException) {
- e.printStackTrace()
- }
- return destFile.absolutePath
- }
-
- fun getSurahDuration(context: Context,path: String): Int {
- val mmr = MediaMetadataRetriever()
- mmr.setDataSource(context, Uri.parse(path))
- val durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
- return durationStr!!.toInt()
- }
-
- fun getSurahAudioPath(audioPathInfo: AudioPathInfo, surah: Int): String? {
- return String.format(Locale.US, audioPathInfo.urlFormat, surah)
- }
-
companion object {
const val ZIP_EXTENSION = ".zip"
const val AUDIO_EXTENSION = ".mp3"
diff --git a/feature/audio/build.gradle b/feature/audio/build.gradle
index 43eeea22b8..b04dd3534e 100644
--- a/feature/audio/build.gradle
+++ b/feature/audio/build.gradle
@@ -23,6 +23,7 @@ android {
dependencies {
implementation project(path: ':common:audio')
+ implementation project(path: ':common:data')
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${coroutinesVersion}"
@@ -36,4 +37,6 @@ dependencies {
testImplementation "junit:junit:${junitVersion}"
testImplementation "com.google.truth:truth:${truthVersion}"
+
+ implementation 'com.jakewharton.timber:timber:5.0.1'
}
diff --git a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/database/SuraTimingDatabaseHandler.kt b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/database/SuraTimingDatabaseHandler.kt
new file mode 100644
index 0000000000..70db297573
--- /dev/null
+++ b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/database/SuraTimingDatabaseHandler.kt
@@ -0,0 +1,126 @@
+package com.quran.labs.androidquran.feature.audio.database
+
+import android.database.Cursor
+import android.database.DefaultDatabaseErrorHandler
+import android.database.sqlite.SQLiteDatabase
+import android.database.sqlite.SQLiteDatabaseCorruptException
+import timber.log.Timber
+import java.io.File
+import java.lang.Exception
+import java.sql.SQLException
+import java.util.HashMap
+
+class SuraTimingDatabaseHandler private constructor(path: String) {
+ private var database: SQLiteDatabase? = null
+
+ object TimingsTable {
+ const val TABLE_NAME = "timings"
+ const val COL_SURA = "sura"
+ const val COL_AYAH = "ayah"
+ const val COL_TIME = "time"
+ }
+
+ object PropertiesTable {
+ const val TABLE_NAME = "properties"
+ const val COL_PROPERTY = "property"
+ const val COL_VALUE = "value"
+ }
+
+ companion object {
+ private val databaseMap: MutableMap = HashMap()
+
+ @JvmStatic
+ @Synchronized
+ fun getDatabaseHandler(path: String): SuraTimingDatabaseHandler {
+ var handler = databaseMap[path]
+ if (handler == null) {
+ handler = SuraTimingDatabaseHandler(path)
+ databaseMap[path] = handler
+ }
+ return handler
+ }
+
+ @Synchronized
+ fun clearDatabaseHandlerIfExists(databasePath: String) {
+ try {
+ val handler = databaseMap.remove(databasePath)
+ if (handler != null) {
+ handler.database?.close()
+ databaseMap.remove(databasePath)
+ }
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+ }
+ }
+
+ init {
+ Timber.d("opening gapless data file, %s", path)
+ database = try {
+ SQLiteDatabase.openDatabase(
+ path, null,
+ SQLiteDatabase.NO_LOCALIZED_COLLATORS, DefaultDatabaseErrorHandler()
+ )
+ } catch (sce: SQLiteDatabaseCorruptException) {
+ Timber.d("database corrupted: %s", path)
+ null
+ } catch (se: SQLException) {
+ Timber.d("database at $path ${if (File(path).exists()) "exists " else "doesn 't exist"}")
+ Timber.e(se)
+ null
+ }
+ }
+
+ private fun validDatabase(): Boolean = database?.isOpen ?: false
+
+ fun getAyahTimings(sura: Int): Cursor? {
+ if (!validDatabase()) return null
+
+ return try {
+ database?.query(
+ TimingsTable.TABLE_NAME, arrayOf(
+ TimingsTable.COL_SURA,
+ TimingsTable.COL_AYAH, TimingsTable.COL_TIME
+ ),
+ TimingsTable.COL_SURA + "=" + sura,
+ null, null, null, TimingsTable.COL_AYAH + " ASC"
+ )
+ } catch (e: Exception) {
+ null
+ }
+ }
+
+ fun getVersion(): Int {
+ if (!validDatabase()) {
+ return -1
+ }
+ var cursor: Cursor? = null
+ return try {
+ cursor = database?.query(
+ PropertiesTable.TABLE_NAME, arrayOf(PropertiesTable.COL_VALUE),
+ PropertiesTable.COL_PROPERTY + "= 'version'", null, null, null, null
+ )
+ if (cursor != null && cursor.moveToFirst()) {
+ cursor.getInt(0)
+ } else {
+ 1
+ }
+ } catch (e: Exception) {
+ 1
+ } finally {
+ DatabaseUtils.closeCursor(cursor)
+ }
+ }
+
+ object DatabaseUtils {
+ @JvmStatic
+ fun closeCursor(cursor: Cursor?) {
+ try {
+ cursor?.close()
+ } catch (e: Exception) {
+ // no op
+ }
+ }
+ }
+}
+
diff --git a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
new file mode 100644
index 0000000000..95c7d8062f
--- /dev/null
+++ b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
@@ -0,0 +1,259 @@
+package com.quran.labs.androidquran.feature.audio.util
+
+import android.content.Context
+import android.database.Cursor
+import android.database.SQLException
+import android.media.MediaMetadataRetriever
+import android.net.Uri
+import android.os.Environment
+import android.util.SparseIntArray
+import com.quran.data.model.SuraAyah
+import com.quran.labs.androidquran.common.audio.model.QariItem
+import com.quran.labs.androidquran.feature.audio.database.SuraTimingDatabaseHandler
+import com.quran.labs.androidquran.feature.audio.database.SuraTimingDatabaseHandler.DatabaseUtils.closeCursor
+import com.quran.labs.androidquran.feature.audio.util.soundfile.CheapSoundFile
+import kotlinx.coroutines.*
+import okio.BufferedSink
+import okio.buffer
+import okio.sink
+import okio.source
+import timber.log.Timber
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.util.Locale
+import java.util.UUID
+import kotlin.math.roundToInt
+
+class AudioShareUtils {
+
+ val audioCacheDirectory = File(
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).path +
+ File.separator + "quran_android_cache")
+ var audioCacheFilePaths: MutableList = ArrayList()
+ var selectedQari: QariItem? = null
+ var selectedStartSuraAyah: SuraAyah? = null
+ var selectedEndSuraAyah: SuraAyah? = null
+
+
+ data class ProperAyahOrder(val start: SuraAyah, val end: SuraAyah)
+
+ private fun getReorderedAyahPair(start: SuraAyah, end: SuraAyah): ProperAyahOrder {
+ val (actualStart, actualEnd) = if (start <= end) {
+ start to end
+ } else {
+ end to start
+ }
+ return ProperAyahOrder(actualStart, actualEnd)
+ }
+
+
+ fun createSharableAudioFile(context: Context, start: SuraAyah, end: SuraAyah, qari: QariItem, urlFormat: String, gaplessDatabase: String): String? {
+ selectedStartSuraAyah = getReorderedAyahPair(start, end).start
+ selectedEndSuraAyah = getReorderedAyahPair(start, end).end
+ selectedQari = qari
+
+ var sharablePath: String? = null
+
+ return runBlocking {
+ GlobalScope.launch(Dispatchers.IO) {
+ val mapArray = async {
+ getTimingData(start, end, gaplessDatabase)
+ }.await()
+
+ val startAyah = start.ayah
+ val endAyah = end.ayah
+ val startSurahTimingDataArray: SparseIntArray = mapArray[0]
+ val endSurahTimingDataArray: SparseIntArray = mapArray[1]
+
+ val isFirstAyahInSurah = startAyah == 1
+ val startTimeOfAyahAfterEndAyah = endSurahTimingDataArray[endAyah + 1]
+ val isLastAyahInSurah = startTimeOfAyahAfterEndAyah == 0
+
+ val startAyahTime = if (isFirstAyahInSurah) {
+ 0
+ } else {
+ startSurahTimingDataArray[startAyah]
+ }
+
+ val endAyahTime = if (isLastAyahInSurah) {
+ getSurahDuration(context,
+ getSurahAudioPath(urlFormat, end.sura)!!)
+ } else {
+ startTimeOfAyahAfterEndAyah
+ }
+
+ val startAndEndAyahAreInSameSurah = start.sura == end.sura
+
+ if (startAndEndAyahAreInSameSurah) {
+ val audioSegmentPath: String = getSurahSegment(
+ getSurahAudioPath(urlFormat, start.sura)!!, startAyahTime,
+ endAyahTime)!!
+ audioCacheFilePaths.add(audioSegmentPath)
+ sharablePath = getRenamedSharableAudioFile(audioSegmentPath)
+ } else {
+ val segmentPaths = java.util.ArrayList()
+ val endOfSurah = -1
+ val startOfSurah = 0
+ val startSegmentPath: String = getSurahSegmentPath(context, urlFormat, start.sura,
+ startAyahTime, endOfSurah)
+ val lastSegmentPath: String = getSurahSegmentPath(context, urlFormat, end.sura,
+ startOfSurah, endAyahTime)
+
+ for (surahIndex in start.sura..end.sura) {
+ val isTheFirstSurah = surahIndex == start.sura
+ val isMiddleSurah = surahIndex != start.sura && surahIndex != end.sura
+ if (isTheFirstSurah) {
+ segmentPaths.add(startSegmentPath)
+ audioCacheFilePaths.add(startSegmentPath)
+ continue
+ }
+ if (isMiddleSurah) {
+ segmentPaths.add(getSurahAudioPath(urlFormat, surahIndex)!!)
+ continue
+ }
+ segmentPaths.add(lastSegmentPath)
+ audioCacheFilePaths.add(lastSegmentPath)
+ }
+
+ val audioSegmentsWereCreated = segmentPaths.isNotEmpty()
+
+ if (audioSegmentsWereCreated) {
+ val sharableAudioFilePath: String = getMergedAudioFromSegments(
+ segmentPaths)
+ sharablePath = getRenamedSharableAudioFile(sharableAudioFilePath)
+ } else {
+ sharablePath = null
+ }
+ }
+ }.join()
+ return@runBlocking sharablePath
+ }
+
+ }
+
+ private fun getSurahSegmentPath(context: Context, urlFormat: String, surah: Int,
+ startAyahTime: Int, endAyahTime: Int): String {
+ var upperBoundTime = endAyahTime
+ val audioFilePath: String = getSurahAudioPath(urlFormat, surah)!!
+ val isFirstSegment = endAyahTime < 0
+ if (isFirstSegment) {
+ upperBoundTime = getSurahDuration(context, audioFilePath)
+ }
+ return getSurahSegment(audioFilePath, startAyahTime, upperBoundTime)!!
+ }
+
+ private fun getRenamedSharableAudioFile(audioSegmentPath: String): String {
+ val newAudioFileName: String = selectedQari!!.path + "_" + selectedStartSuraAyah!!.sura + "-" + selectedStartSuraAyah!!.ayah + "_" + selectedEndSuraAyah!!.sura + "-" + selectedEndSuraAyah!!.ayah
+ val newAudioFilePath: String = audioCacheDirectory.toString() + File.separator + newAudioFileName + ".mp3"
+ File(audioSegmentPath).renameTo(File(newAudioFilePath))
+ audioCacheFilePaths.remove(audioSegmentPath)
+ for (path in audioCacheFilePaths) {
+ File(path).delete()
+ }
+ audioCacheFilePaths.clear()
+ return newAudioFilePath
+ }
+
+ private fun getSurahDuration(context: Context, path: String): Int {
+ val mmr = MediaMetadataRetriever()
+ mmr.setDataSource(context, Uri.parse(path))
+ val durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
+ return durationStr!!.toInt()
+ }
+
+ private fun getSurahAudioPath(urlFormat: String, surah: Int): String? {
+ return String.format(Locale.US, urlFormat, surah)
+ }
+
+ private fun getTimingData(start: SuraAyah, end: SuraAyah, gaplessDatabase: String): ArrayList {
+ val databasePath = gaplessDatabase!!
+
+ val db: SuraTimingDatabaseHandler = SuraTimingDatabaseHandler.getDatabaseHandler(
+ databasePath)
+ var firstSurahMap = SparseIntArray()
+ var lastSurahMap = SparseIntArray()
+ var firstSurahCursor: Cursor? = null
+ var lastSurahCursor: Cursor? = null
+
+ try {
+ firstSurahCursor = db.getAyahTimings(start.sura)
+ firstSurahMap = populateArrayFromCursor(firstSurahCursor)!!
+ lastSurahCursor = db.getAyahTimings(end.sura)
+ lastSurahMap = populateArrayFromCursor(lastSurahCursor)
+ } catch (sqlException: SQLException) {
+ Timber.e(sqlException)
+ } finally {
+ closeCursor(firstSurahCursor)
+ closeCursor(lastSurahCursor)
+ }
+
+ return ArrayList(
+ listOf(firstSurahMap, lastSurahMap))
+ }
+
+ private fun populateArrayFromCursor(cursor: Cursor?): SparseIntArray {
+ val sparseIntArray = SparseIntArray()
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ val ayah = cursor.getInt(1)
+ val time = cursor.getInt(2)
+ sparseIntArray.put(ayah, time)
+ } while (cursor.moveToNext())
+ }
+ return sparseIntArray
+ }
+
+ private fun getSurahSegment(path: String, lowerCut: Int, upperCut: Int): String? {
+ if (lowerCut == 0 && upperCut == 0) {
+ return null
+ }
+ val tempAudioName = UUID.randomUUID().toString() + ".mp3"
+ val destFile = File(audioCacheDirectory.path + File.separator + tempAudioName)
+ val mSoundFile = arrayOfNulls(1)
+ try {
+ mSoundFile[0] = CheapSoundFile.create(path, null)
+ val startTime = lowerCut.toFloat() / 1000
+ val endTime = upperCut.toFloat() / 1000
+ val samplesPerFrame = mSoundFile[0]?.samplesPerFrame
+ val sampleRate = mSoundFile[0]?.sampleRate
+ val avg = sampleRate?.div(samplesPerFrame!!)
+ val startFrames = (startTime * avg!!).roundToInt()
+ val endFrames = (endTime * avg).roundToInt()
+ mSoundFile[0]?.WriteFile(destFile, startFrames, endFrames - startFrames)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ return destFile.absolutePath
+ }
+
+ private fun getMergedAudioFromSegments(segments: ArrayList): String {
+ var mergedAudioPath = segments[0]
+ if (segments.size > 1) {
+ for (i in 1 until segments.size) {
+ mergedAudioPath = mergeAudios(mergedAudioPath, segments[i])!!
+ audioCacheFilePaths.add(mergedAudioPath)
+ }
+ }
+ return mergedAudioPath
+ }
+
+ private fun mergeAudios(path1: String, path2: String): String? {
+ val tempAudioName = UUID.randomUUID().toString() + ".mp3"
+ val destFile = File(audioCacheDirectory.path + File.separator + tempAudioName)
+ try {
+ val bufferedSink: BufferedSink = destFile.sink().buffer()
+ bufferedSink.writeAll(File(path1).source())
+ bufferedSink.writeAll(File(path2).source())
+ bufferedSink.close()
+ return destFile.path
+ } catch (e2: FileNotFoundException) {
+ e2.printStackTrace()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ return null
+ }
+
+
+}
diff --git a/app/src/main/java/com/quran/labs/androidquran/util/audioConversionUtils/CheapMP3.java b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapMP3.java
similarity index 99%
rename from app/src/main/java/com/quran/labs/androidquran/util/audioConversionUtils/CheapMP3.java
rename to feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapMP3.java
index 191a256093..bedbc1af1c 100644
--- a/app/src/main/java/com/quran/labs/androidquran/util/audioConversionUtils/CheapMP3.java
+++ b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapMP3.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.quran.labs.androidquran.util.audioConversionUtils;
+package com.quran.labs.androidquran.feature.audio.util.soundfile;
import java.io.File;
import java.io.FileInputStream;
diff --git a/app/src/main/java/com/quran/labs/androidquran/util/audioConversionUtils/CheapSoundFile.java b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapSoundFile.java
similarity index 97%
rename from app/src/main/java/com/quran/labs/androidquran/util/audioConversionUtils/CheapSoundFile.java
rename to feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapSoundFile.java
index 9a8dc9b368..9ea9dd27fa 100644
--- a/app/src/main/java/com/quran/labs/androidquran/util/audioConversionUtils/CheapSoundFile.java
+++ b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapSoundFile.java
@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.quran.labs.androidquran.util.audioConversionUtils;
+package com.quran.labs.androidquran.feature.audio.util.soundfile;
import java.io.File;
import java.io.FileInputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;
+
/**
* CheapSoundFile is the parent class of several subclasses that each
* do a "cheap" scan of various sound file formats, parsing as little
@@ -203,7 +204,7 @@ public String computeMd5OfFirst10Frames()
numFrames = 10;
}
- MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
+ MessageDigest digest = MessageDigest.getInstance("MD5");
FileInputStream in = new FileInputStream(mInputFile);
int pos = 0;
for (int i = 0; i < numFrames; i++) {
From 168a044da317320dcb87fb9120e4a97d77660497 Mon Sep 17 00:00:00 2001
From: Doozy
Date: Sat, 30 Jul 2022 11:30:01 +0300
Subject: [PATCH 12/18] removed greek prefix
---
.../androidquran/feature/audio/util/AudioShareUtils.kt | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
index 95c7d8062f..e28d0633cd 100644
--- a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
+++ b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
@@ -210,17 +210,17 @@ class AudioShareUtils {
}
val tempAudioName = UUID.randomUUID().toString() + ".mp3"
val destFile = File(audioCacheDirectory.path + File.separator + tempAudioName)
- val mSoundFile = arrayOfNulls(1)
+ val soundFile = arrayOfNulls(1)
try {
- mSoundFile[0] = CheapSoundFile.create(path, null)
+ soundFile[0] = CheapSoundFile.create(path, null)
val startTime = lowerCut.toFloat() / 1000
val endTime = upperCut.toFloat() / 1000
- val samplesPerFrame = mSoundFile[0]?.samplesPerFrame
- val sampleRate = mSoundFile[0]?.sampleRate
+ val samplesPerFrame = soundFile[0]?.samplesPerFrame
+ val sampleRate = soundFile[0]?.sampleRate
val avg = sampleRate?.div(samplesPerFrame!!)
val startFrames = (startTime * avg!!).roundToInt()
val endFrames = (endTime * avg).roundToInt()
- mSoundFile[0]?.WriteFile(destFile, startFrames, endFrames - startFrames)
+ soundFile[0]?.WriteFile(destFile, startFrames, endFrames - startFrames)
} catch (e: IOException) {
e.printStackTrace()
}
From e85760ce3938571bff2be8f325d8bfc3eee8b366 Mon Sep 17 00:00:00 2001
From: Doozy
Date: Sat, 30 Jul 2022 11:48:31 +0300
Subject: [PATCH 13/18] check cache directory existance at sharing
---
.../quran/labs/androidquran/ui/PagerActivity.java | 14 +++++---------
.../feature/audio/util/AudioShareUtils.kt | 8 ++++++++
2 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
index 891ab2c90d..28c6b982b5 100644
--- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
+++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
@@ -263,9 +263,6 @@ public class PagerActivity extends AppCompatActivity implements
private final PagerHandler handler = new PagerHandler(this);
- public static final File audioCacheDirectory = new File(
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath() +
- File.separator + "quran_android_cache");
private ArrayList audioCacheFilePaths = new ArrayList<>();
private SuraAyah selectedStartSuraAyah = null;
private SuraAyah selectedEndSuraAyah = null;
@@ -367,11 +364,6 @@ public void onCreate(Bundle savedInstanceState) {
compositeDisposable = new CompositeDisposable();
setContentView(R.layout.quran_page_activity_slider);
- if (!audioCacheDirectory.exists()) {
- if (!audioCacheDirectory.mkdirs()) {
- Toast.makeText(PagerActivity.this, "could not create directory", Toast.LENGTH_SHORT).show();
- }
- }
audioStatusBar = findViewById(R.id.audio_area);
audioStatusBar.setIsDualPageMode(quranScreenInfo.isDualPageMode());
audioStatusBar.setQariList(audioUtils.getQariList(this));
@@ -1907,7 +1899,11 @@ public void shareAyahAudio(SuraAyah start, SuraAyah end) {
String path = audioShareUtils.createSharableAudioFile(this, selectedStartSuraAyah,
selectedEndSuraAyah, selectedQari, audioPathInfo.getUrlFormat(),
audioPathInfo.getGaplessDatabase());
- shareAudioSegment(path);
+ if(path != null && !path.isEmpty()){
+ shareAudioSegment(path);
+ }else{
+ Toast.makeText(this, "could not share audio ayah", Toast.LENGTH_SHORT).show();
+ }
} else {
requestDownload(audioPathInfo);
}
diff --git a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
index e28d0633cd..f36310be79 100644
--- a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
+++ b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
@@ -7,6 +7,7 @@ import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Environment
import android.util.SparseIntArray
+import android.widget.Toast
import com.quran.data.model.SuraAyah
import com.quran.labs.androidquran.common.audio.model.QariItem
import com.quran.labs.androidquran.feature.audio.database.SuraTimingDatabaseHandler
@@ -53,6 +54,13 @@ class AudioShareUtils {
selectedEndSuraAyah = getReorderedAyahPair(start, end).end
selectedQari = qari
+ if (!audioCacheDirectory.exists()) {
+ if (!audioCacheDirectory.mkdirs()) {
+ Toast.makeText(context, "could not create directory", Toast.LENGTH_SHORT).show()
+ return null;
+ }
+ }
+
var sharablePath: String? = null
return runBlocking {
From d83cfbd73023ab119c830356894df578bcb83bdd Mon Sep 17 00:00:00 2001
From: Ahmed El-Helw
Date: Sun, 11 Sep 2022 16:47:03 +0400
Subject: [PATCH 14/18] Move audio sharing to a separate feature module
---
app/build.gradle | 4 +-
.../data/AyahInfoDatabaseHandler.java | 2 +-
.../androidquran/data/QuranDataProvider.java | 2 +-
.../database/AudioDatabaseVersionChecker.kt | 1 +
.../androidquran/database/DatabaseHandler.kt | 1 +
.../translation/ArabicDatabaseUtils.java | 2 +-
.../labs/androidquran/service/AudioService.kt | 6 +-
.../labs/androidquran/ui/PagerActivity.java | 4 +-
.../androidquran/worker/AudioUpdateWorker.kt | 2 +-
build.gradle | 1 +
common/audio/build.gradle | 2 +
.../timing}/SuraTimingDatabaseHandler.kt | 5 +-
common/util/build.gradle | 13 +
common/util/src/main/AndroidManifest.xml | 1 +
.../common/util}/database/DatabaseUtils.kt | 2 +-
feature/audio/build.gradle | 2 -
.../database/SuraTimingDatabaseHandler.kt | 126 --------
.../feature/audio/util/AudioShareUtils.kt | 267 ----------------
feature/audioshare/build.gradle | 43 +++
.../audioshare/src/main/AndroidManifest.xml | 1 +
.../feature/audioshare/AudioShareUtils.kt | 302 ++++++++++++++++++
.../audioshare}/soundfile/CheapMP3.java | 2 +-
.../audioshare}/soundfile/CheapSoundFile.java | 2 +-
settings.gradle | 2 +
24 files changed, 384 insertions(+), 411 deletions(-)
rename {app/src/main/java/com/quran/labs/androidquran/database => common/audio/src/main/java/com/quran/labs/androidquran/common/audio/timing}/SuraTimingDatabaseHandler.kt (96%)
create mode 100644 common/util/build.gradle
create mode 100644 common/util/src/main/AndroidManifest.xml
rename {app/src/main/java/com/quran/labs/androidquran => common/util/src/main/java/com/quran/common/util}/database/DatabaseUtils.kt (83%)
delete mode 100644 feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/database/SuraTimingDatabaseHandler.kt
delete mode 100644 feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
create mode 100644 feature/audioshare/build.gradle
create mode 100644 feature/audioshare/src/main/AndroidManifest.xml
create mode 100644 feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt
rename feature/{audio/src/main/java/com/quran/labs/androidquran/feature/audio/util => audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare}/soundfile/CheapMP3.java (99%)
rename feature/{audio/src/main/java/com/quran/labs/androidquran/feature/audio/util => audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare}/soundfile/CheapSoundFile.java (99%)
diff --git a/app/build.gradle b/app/build.gradle
index 0662fe0044..ff023ad3fa 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -129,8 +129,10 @@ dependencies {
implementation project(path: ':common:toolbar')
implementation project(path: ':common:upgrade')
implementation project(path: ':common:ui:core')
+ implementation project(path: ':common:util')
implementation project(path: ':feature:audio')
+ implementation project(path: ':feature:audioshare')
implementation project(path: ':feature:downloadmanager')
implementation project(path: ':feature:qarilist')
@@ -170,7 +172,7 @@ dependencies {
kapt("com.squareup.moshi:moshi-kotlin-codegen:${moshiVersion}")
implementation "dev.chrisbanes:insetter-ktx:0.3.1"
- implementation 'com.jakewharton.timber:timber:5.0.1'
+ implementation "com.jakewharton.timber:timber:${timberVersion}"
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
implementation 'com.google.firebase:firebase-crashlytics:18.2.13'
diff --git a/app/src/main/java/com/quran/labs/androidquran/data/AyahInfoDatabaseHandler.java b/app/src/main/java/com/quran/labs/androidquran/data/AyahInfoDatabaseHandler.java
index 79040def61..be1715f33d 100644
--- a/app/src/main/java/com/quran/labs/androidquran/data/AyahInfoDatabaseHandler.java
+++ b/app/src/main/java/com/quran/labs/androidquran/data/AyahInfoDatabaseHandler.java
@@ -7,7 +7,7 @@
import android.graphics.RectF;
import androidx.annotation.NonNull;
-import com.quran.labs.androidquran.database.DatabaseUtils;
+import com.quran.common.util.database.DatabaseUtils;
import com.quran.labs.androidquran.util.QuranFileUtils;
import com.quran.page.common.data.AyahBounds;
import com.quran.page.common.data.AyahCoordinates;
diff --git a/app/src/main/java/com/quran/labs/androidquran/data/QuranDataProvider.java b/app/src/main/java/com/quran/labs/androidquran/data/QuranDataProvider.java
index e7da9e8bb6..43e96f1dee 100644
--- a/app/src/main/java/com/quran/labs/androidquran/data/QuranDataProvider.java
+++ b/app/src/main/java/com/quran/labs/androidquran/data/QuranDataProvider.java
@@ -11,13 +11,13 @@
import android.net.Uri;
import android.provider.BaseColumns;
+import com.quran.common.util.database.DatabaseUtils;
import com.quran.data.core.QuranInfo;
import com.quran.labs.androidquran.BuildConfig;
import com.quran.labs.androidquran.QuranApplication;
import com.quran.labs.androidquran.R;
import com.quran.labs.androidquran.common.LocalTranslation;
import com.quran.labs.androidquran.database.DatabaseHandler;
-import com.quran.labs.androidquran.database.DatabaseUtils;
import com.quran.labs.androidquran.database.TranslationsDBAdapter;
import com.quran.labs.androidquran.util.QuranFileUtils;
import com.quran.labs.androidquran.util.QuranUtils;
diff --git a/app/src/main/java/com/quran/labs/androidquran/database/AudioDatabaseVersionChecker.kt b/app/src/main/java/com/quran/labs/androidquran/database/AudioDatabaseVersionChecker.kt
index 1bb60957e8..964cf60d7f 100644
--- a/app/src/main/java/com/quran/labs/androidquran/database/AudioDatabaseVersionChecker.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/database/AudioDatabaseVersionChecker.kt
@@ -1,5 +1,6 @@
package com.quran.labs.androidquran.database
+import com.quran.labs.androidquran.common.audio.timing.SuraTimingDatabaseHandler
import com.quran.labs.androidquran.feature.audio.VersionableDatabaseChecker
import javax.inject.Inject
diff --git a/app/src/main/java/com/quran/labs/androidquran/database/DatabaseHandler.kt b/app/src/main/java/com/quran/labs/androidquran/database/DatabaseHandler.kt
index ccb254ea9b..f1b241ed71 100644
--- a/app/src/main/java/com/quran/labs/androidquran/database/DatabaseHandler.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/database/DatabaseHandler.kt
@@ -13,6 +13,7 @@ import androidx.core.content.ContextCompat
import com.quran.common.search.ArabicSearcher
import com.quran.common.search.DefaultSearcher
import com.quran.common.search.Searcher
+import com.quran.common.util.database.DatabaseUtils
import com.quran.data.model.QuranText
import com.quran.data.model.VerseRange
import com.quran.labs.androidquran.R
diff --git a/app/src/main/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtils.java b/app/src/main/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtils.java
index 54937c461a..3da020fcd3 100644
--- a/app/src/main/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtils.java
+++ b/app/src/main/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtils.java
@@ -3,6 +3,7 @@
import android.content.Context;
import android.database.Cursor;
+import com.quran.common.util.database.DatabaseUtils;
import com.quran.data.core.QuranInfo;
import com.quran.data.model.QuranText;
import com.quran.data.model.bookmark.Bookmark;
@@ -10,7 +11,6 @@
import com.quran.labs.androidquran.data.QuranFileConstants;
import com.quran.data.model.SuraAyah;
import com.quran.labs.androidquran.database.DatabaseHandler;
-import com.quran.labs.androidquran.database.DatabaseUtils;
import com.quran.labs.androidquran.util.QuranFileUtils;
import java.util.ArrayList;
diff --git a/app/src/main/java/com/quran/labs/androidquran/service/AudioService.kt b/app/src/main/java/com/quran/labs/androidquran/service/AudioService.kt
index 06ac074ba6..d1d9c514d0 100644
--- a/app/src/main/java/com/quran/labs/androidquran/service/AudioService.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/service/AudioService.kt
@@ -57,17 +57,17 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.media.session.MediaButtonReceiver
+import com.quran.common.util.database.DatabaseUtils
import com.quran.data.core.QuranInfo
import com.quran.data.model.SuraAyah
import com.quran.labs.androidquran.QuranApplication
import com.quran.labs.androidquran.R
+import com.quran.labs.androidquran.common.audio.timing.SuraTimingDatabaseHandler.Companion.getDatabaseHandler
import com.quran.labs.androidquran.dao.audio.AudioPlaybackInfo
import com.quran.labs.androidquran.dao.audio.AudioRequest
import com.quran.labs.androidquran.data.Constants
import com.quran.labs.androidquran.data.QuranDisplayData
import com.quran.labs.androidquran.data.QuranFileConstants
-import com.quran.labs.androidquran.database.DatabaseUtils.closeCursor
-import com.quran.labs.androidquran.database.SuraTimingDatabaseHandler.Companion.getDatabaseHandler
import com.quran.labs.androidquran.extension.requiresBasmallah
import com.quran.labs.androidquran.presenter.audio.service.AudioQueue
import com.quran.labs.androidquran.service.util.AudioFocusHelper
@@ -427,7 +427,7 @@ class AudioService : Service(), OnCompletionListener, OnPreparedListener,
// don't crash the app if the database is corrupt
Timber.e(se)
} finally {
- closeCursor(cursor)
+ DatabaseUtils.closeCursor(cursor)
}
map
diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
index 92bc163008..5cd5fa5505 100644
--- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
+++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
@@ -80,7 +80,7 @@
import com.quran.labs.androidquran.di.component.activity.PagerActivityComponent;
import com.quran.labs.androidquran.di.module.activity.PagerActivityModule;
import com.quran.labs.androidquran.di.module.fragment.QuranPageModule;
-import com.quran.labs.androidquran.feature.audio.util.AudioShareUtils;
+import com.quran.labs.androidquran.feature.audioshare.AudioShareUtils;
import com.quran.labs.androidquran.model.bookmark.BookmarkModel;
import com.quran.labs.androidquran.model.translation.ArabicDatabaseUtils;
import com.quran.labs.androidquran.presenter.audio.AudioPresenter;
@@ -1917,7 +1917,7 @@ public void shareAyahAudio(SuraAyah start, SuraAyah end) {
if (gaplessDatabaseExists) {
if (audioFilesExist(audioPathInfo)) {
AudioShareUtils audioShareUtils = new AudioShareUtils();
- String path = audioShareUtils.createSharableAudioFile(this, selectedStartSuraAyah,
+ String path = audioShareUtils.createBlockingSharableAudioFile(this, selectedStartSuraAyah,
selectedEndSuraAyah, selectedQari, audioPathInfo.getUrlFormat(),
audioPathInfo.getGaplessDatabase());
if(path != null && !path.isEmpty()){
diff --git a/app/src/main/java/com/quran/labs/androidquran/worker/AudioUpdateWorker.kt b/app/src/main/java/com/quran/labs/androidquran/worker/AudioUpdateWorker.kt
index 2801e3d249..06111025f1 100644
--- a/app/src/main/java/com/quran/labs/androidquran/worker/AudioUpdateWorker.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/worker/AudioUpdateWorker.kt
@@ -11,7 +11,7 @@ import com.quran.labs.androidquran.R
import com.quran.labs.androidquran.core.worker.WorkerTaskFactory
import com.quran.labs.androidquran.data.Constants
import com.quran.labs.androidquran.database.AudioDatabaseVersionChecker
-import com.quran.labs.androidquran.database.SuraTimingDatabaseHandler
+import com.quran.labs.androidquran.common.audio.timing.SuraTimingDatabaseHandler
import com.quran.labs.androidquran.feature.audio.AudioUpdater
import com.quran.labs.androidquran.feature.audio.api.AudioUpdateService
import com.quran.labs.androidquran.feature.audio.util.AudioFileCheckerImpl
diff --git a/build.gradle b/build.gradle
index a3af6b15dd..172a41322e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -21,6 +21,7 @@ buildscript {
workManagerVersion = '2.7.1'
materialComponentsVersion = '1.6.1'
coreKtxVersion = '1.7.0'
+ timberVersion = '5.0.1'
anvilVersion = '2.4.2'
moshiVersion = '1.14.0'
diff --git a/common/audio/build.gradle b/common/audio/build.gradle
index 123f2cdaba..8ea48773cd 100644
--- a/common/audio/build.gradle
+++ b/common/audio/build.gradle
@@ -16,11 +16,13 @@ android {
dependencies {
implementation project(":common:data")
implementation project(":common:download")
+ implementation project(path: ':common:util')
implementation deps.dagger.runtime
implementation "androidx.annotation:annotation:${androidxAnnotationVersion}"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}"
implementation "com.squareup.okio:okio:${okioVersion}"
+ implementation "com.jakewharton.timber:timber:${timberVersion}"
testImplementation "junit:junit:${junitVersion}"
testImplementation "com.google.truth:truth:${truthVersion}"
diff --git a/app/src/main/java/com/quran/labs/androidquran/database/SuraTimingDatabaseHandler.kt b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/timing/SuraTimingDatabaseHandler.kt
similarity index 96%
rename from app/src/main/java/com/quran/labs/androidquran/database/SuraTimingDatabaseHandler.kt
rename to common/audio/src/main/java/com/quran/labs/androidquran/common/audio/timing/SuraTimingDatabaseHandler.kt
index 42e06615b2..76e732b385 100644
--- a/app/src/main/java/com/quran/labs/androidquran/database/SuraTimingDatabaseHandler.kt
+++ b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/timing/SuraTimingDatabaseHandler.kt
@@ -1,14 +1,13 @@
-package com.quran.labs.androidquran.database
+package com.quran.labs.androidquran.common.audio.timing
import android.database.Cursor
import android.database.DefaultDatabaseErrorHandler
import android.database.SQLException
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteDatabaseCorruptException
+import com.quran.common.util.database.DatabaseUtils
import timber.log.Timber
import java.io.File
-import java.lang.Exception
-import java.util.HashMap
class SuraTimingDatabaseHandler private constructor(path: String) {
private var database: SQLiteDatabase? = null
diff --git a/common/util/build.gradle b/common/util/build.gradle
new file mode 100644
index 0000000000..a957916758
--- /dev/null
+++ b/common/util/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion deps.android.build.compileSdkVersion
+ defaultConfig {
+ minSdkVersion deps.android.build.minSdkVersion
+ targetSdkVersion deps.android.build.targetSdkVersion
+ }
+}
+
+dependencies {
+}
diff --git a/common/util/src/main/AndroidManifest.xml b/common/util/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..9b676f398b
--- /dev/null
+++ b/common/util/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/app/src/main/java/com/quran/labs/androidquran/database/DatabaseUtils.kt b/common/util/src/main/java/com/quran/common/util/database/DatabaseUtils.kt
similarity index 83%
rename from app/src/main/java/com/quran/labs/androidquran/database/DatabaseUtils.kt
rename to common/util/src/main/java/com/quran/common/util/database/DatabaseUtils.kt
index c63c2ab132..f7e64b2bd2 100644
--- a/app/src/main/java/com/quran/labs/androidquran/database/DatabaseUtils.kt
+++ b/common/util/src/main/java/com/quran/common/util/database/DatabaseUtils.kt
@@ -1,4 +1,4 @@
-package com.quran.labs.androidquran.database
+package com.quran.common.util.database
import android.database.Cursor
import java.lang.Exception
diff --git a/feature/audio/build.gradle b/feature/audio/build.gradle
index b04dd3534e..68c0dead19 100644
--- a/feature/audio/build.gradle
+++ b/feature/audio/build.gradle
@@ -37,6 +37,4 @@ dependencies {
testImplementation "junit:junit:${junitVersion}"
testImplementation "com.google.truth:truth:${truthVersion}"
-
- implementation 'com.jakewharton.timber:timber:5.0.1'
}
diff --git a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/database/SuraTimingDatabaseHandler.kt b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/database/SuraTimingDatabaseHandler.kt
deleted file mode 100644
index 70db297573..0000000000
--- a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/database/SuraTimingDatabaseHandler.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.quran.labs.androidquran.feature.audio.database
-
-import android.database.Cursor
-import android.database.DefaultDatabaseErrorHandler
-import android.database.sqlite.SQLiteDatabase
-import android.database.sqlite.SQLiteDatabaseCorruptException
-import timber.log.Timber
-import java.io.File
-import java.lang.Exception
-import java.sql.SQLException
-import java.util.HashMap
-
-class SuraTimingDatabaseHandler private constructor(path: String) {
- private var database: SQLiteDatabase? = null
-
- object TimingsTable {
- const val TABLE_NAME = "timings"
- const val COL_SURA = "sura"
- const val COL_AYAH = "ayah"
- const val COL_TIME = "time"
- }
-
- object PropertiesTable {
- const val TABLE_NAME = "properties"
- const val COL_PROPERTY = "property"
- const val COL_VALUE = "value"
- }
-
- companion object {
- private val databaseMap: MutableMap = HashMap()
-
- @JvmStatic
- @Synchronized
- fun getDatabaseHandler(path: String): SuraTimingDatabaseHandler {
- var handler = databaseMap[path]
- if (handler == null) {
- handler = SuraTimingDatabaseHandler(path)
- databaseMap[path] = handler
- }
- return handler
- }
-
- @Synchronized
- fun clearDatabaseHandlerIfExists(databasePath: String) {
- try {
- val handler = databaseMap.remove(databasePath)
- if (handler != null) {
- handler.database?.close()
- databaseMap.remove(databasePath)
- }
- } catch (e: Exception) {
- Timber.e(e)
- }
- }
- }
-
- init {
- Timber.d("opening gapless data file, %s", path)
- database = try {
- SQLiteDatabase.openDatabase(
- path, null,
- SQLiteDatabase.NO_LOCALIZED_COLLATORS, DefaultDatabaseErrorHandler()
- )
- } catch (sce: SQLiteDatabaseCorruptException) {
- Timber.d("database corrupted: %s", path)
- null
- } catch (se: SQLException) {
- Timber.d("database at $path ${if (File(path).exists()) "exists " else "doesn 't exist"}")
- Timber.e(se)
- null
- }
- }
-
- private fun validDatabase(): Boolean = database?.isOpen ?: false
-
- fun getAyahTimings(sura: Int): Cursor? {
- if (!validDatabase()) return null
-
- return try {
- database?.query(
- TimingsTable.TABLE_NAME, arrayOf(
- TimingsTable.COL_SURA,
- TimingsTable.COL_AYAH, TimingsTable.COL_TIME
- ),
- TimingsTable.COL_SURA + "=" + sura,
- null, null, null, TimingsTable.COL_AYAH + " ASC"
- )
- } catch (e: Exception) {
- null
- }
- }
-
- fun getVersion(): Int {
- if (!validDatabase()) {
- return -1
- }
- var cursor: Cursor? = null
- return try {
- cursor = database?.query(
- PropertiesTable.TABLE_NAME, arrayOf(PropertiesTable.COL_VALUE),
- PropertiesTable.COL_PROPERTY + "= 'version'", null, null, null, null
- )
- if (cursor != null && cursor.moveToFirst()) {
- cursor.getInt(0)
- } else {
- 1
- }
- } catch (e: Exception) {
- 1
- } finally {
- DatabaseUtils.closeCursor(cursor)
- }
- }
-
- object DatabaseUtils {
- @JvmStatic
- fun closeCursor(cursor: Cursor?) {
- try {
- cursor?.close()
- } catch (e: Exception) {
- // no op
- }
- }
- }
-}
-
diff --git a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt b/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
deleted file mode 100644
index f36310be79..0000000000
--- a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/AudioShareUtils.kt
+++ /dev/null
@@ -1,267 +0,0 @@
-package com.quran.labs.androidquran.feature.audio.util
-
-import android.content.Context
-import android.database.Cursor
-import android.database.SQLException
-import android.media.MediaMetadataRetriever
-import android.net.Uri
-import android.os.Environment
-import android.util.SparseIntArray
-import android.widget.Toast
-import com.quran.data.model.SuraAyah
-import com.quran.labs.androidquran.common.audio.model.QariItem
-import com.quran.labs.androidquran.feature.audio.database.SuraTimingDatabaseHandler
-import com.quran.labs.androidquran.feature.audio.database.SuraTimingDatabaseHandler.DatabaseUtils.closeCursor
-import com.quran.labs.androidquran.feature.audio.util.soundfile.CheapSoundFile
-import kotlinx.coroutines.*
-import okio.BufferedSink
-import okio.buffer
-import okio.sink
-import okio.source
-import timber.log.Timber
-import java.io.File
-import java.io.FileNotFoundException
-import java.io.IOException
-import java.util.Locale
-import java.util.UUID
-import kotlin.math.roundToInt
-
-class AudioShareUtils {
-
- val audioCacheDirectory = File(
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).path +
- File.separator + "quran_android_cache")
- var audioCacheFilePaths: MutableList = ArrayList()
- var selectedQari: QariItem? = null
- var selectedStartSuraAyah: SuraAyah? = null
- var selectedEndSuraAyah: SuraAyah? = null
-
-
- data class ProperAyahOrder(val start: SuraAyah, val end: SuraAyah)
-
- private fun getReorderedAyahPair(start: SuraAyah, end: SuraAyah): ProperAyahOrder {
- val (actualStart, actualEnd) = if (start <= end) {
- start to end
- } else {
- end to start
- }
- return ProperAyahOrder(actualStart, actualEnd)
- }
-
-
- fun createSharableAudioFile(context: Context, start: SuraAyah, end: SuraAyah, qari: QariItem, urlFormat: String, gaplessDatabase: String): String? {
- selectedStartSuraAyah = getReorderedAyahPair(start, end).start
- selectedEndSuraAyah = getReorderedAyahPair(start, end).end
- selectedQari = qari
-
- if (!audioCacheDirectory.exists()) {
- if (!audioCacheDirectory.mkdirs()) {
- Toast.makeText(context, "could not create directory", Toast.LENGTH_SHORT).show()
- return null;
- }
- }
-
- var sharablePath: String? = null
-
- return runBlocking {
- GlobalScope.launch(Dispatchers.IO) {
- val mapArray = async {
- getTimingData(start, end, gaplessDatabase)
- }.await()
-
- val startAyah = start.ayah
- val endAyah = end.ayah
- val startSurahTimingDataArray: SparseIntArray = mapArray[0]
- val endSurahTimingDataArray: SparseIntArray = mapArray[1]
-
- val isFirstAyahInSurah = startAyah == 1
- val startTimeOfAyahAfterEndAyah = endSurahTimingDataArray[endAyah + 1]
- val isLastAyahInSurah = startTimeOfAyahAfterEndAyah == 0
-
- val startAyahTime = if (isFirstAyahInSurah) {
- 0
- } else {
- startSurahTimingDataArray[startAyah]
- }
-
- val endAyahTime = if (isLastAyahInSurah) {
- getSurahDuration(context,
- getSurahAudioPath(urlFormat, end.sura)!!)
- } else {
- startTimeOfAyahAfterEndAyah
- }
-
- val startAndEndAyahAreInSameSurah = start.sura == end.sura
-
- if (startAndEndAyahAreInSameSurah) {
- val audioSegmentPath: String = getSurahSegment(
- getSurahAudioPath(urlFormat, start.sura)!!, startAyahTime,
- endAyahTime)!!
- audioCacheFilePaths.add(audioSegmentPath)
- sharablePath = getRenamedSharableAudioFile(audioSegmentPath)
- } else {
- val segmentPaths = java.util.ArrayList()
- val endOfSurah = -1
- val startOfSurah = 0
- val startSegmentPath: String = getSurahSegmentPath(context, urlFormat, start.sura,
- startAyahTime, endOfSurah)
- val lastSegmentPath: String = getSurahSegmentPath(context, urlFormat, end.sura,
- startOfSurah, endAyahTime)
-
- for (surahIndex in start.sura..end.sura) {
- val isTheFirstSurah = surahIndex == start.sura
- val isMiddleSurah = surahIndex != start.sura && surahIndex != end.sura
- if (isTheFirstSurah) {
- segmentPaths.add(startSegmentPath)
- audioCacheFilePaths.add(startSegmentPath)
- continue
- }
- if (isMiddleSurah) {
- segmentPaths.add(getSurahAudioPath(urlFormat, surahIndex)!!)
- continue
- }
- segmentPaths.add(lastSegmentPath)
- audioCacheFilePaths.add(lastSegmentPath)
- }
-
- val audioSegmentsWereCreated = segmentPaths.isNotEmpty()
-
- if (audioSegmentsWereCreated) {
- val sharableAudioFilePath: String = getMergedAudioFromSegments(
- segmentPaths)
- sharablePath = getRenamedSharableAudioFile(sharableAudioFilePath)
- } else {
- sharablePath = null
- }
- }
- }.join()
- return@runBlocking sharablePath
- }
-
- }
-
- private fun getSurahSegmentPath(context: Context, urlFormat: String, surah: Int,
- startAyahTime: Int, endAyahTime: Int): String {
- var upperBoundTime = endAyahTime
- val audioFilePath: String = getSurahAudioPath(urlFormat, surah)!!
- val isFirstSegment = endAyahTime < 0
- if (isFirstSegment) {
- upperBoundTime = getSurahDuration(context, audioFilePath)
- }
- return getSurahSegment(audioFilePath, startAyahTime, upperBoundTime)!!
- }
-
- private fun getRenamedSharableAudioFile(audioSegmentPath: String): String {
- val newAudioFileName: String = selectedQari!!.path + "_" + selectedStartSuraAyah!!.sura + "-" + selectedStartSuraAyah!!.ayah + "_" + selectedEndSuraAyah!!.sura + "-" + selectedEndSuraAyah!!.ayah
- val newAudioFilePath: String = audioCacheDirectory.toString() + File.separator + newAudioFileName + ".mp3"
- File(audioSegmentPath).renameTo(File(newAudioFilePath))
- audioCacheFilePaths.remove(audioSegmentPath)
- for (path in audioCacheFilePaths) {
- File(path).delete()
- }
- audioCacheFilePaths.clear()
- return newAudioFilePath
- }
-
- private fun getSurahDuration(context: Context, path: String): Int {
- val mmr = MediaMetadataRetriever()
- mmr.setDataSource(context, Uri.parse(path))
- val durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
- return durationStr!!.toInt()
- }
-
- private fun getSurahAudioPath(urlFormat: String, surah: Int): String? {
- return String.format(Locale.US, urlFormat, surah)
- }
-
- private fun getTimingData(start: SuraAyah, end: SuraAyah, gaplessDatabase: String): ArrayList {
- val databasePath = gaplessDatabase!!
-
- val db: SuraTimingDatabaseHandler = SuraTimingDatabaseHandler.getDatabaseHandler(
- databasePath)
- var firstSurahMap = SparseIntArray()
- var lastSurahMap = SparseIntArray()
- var firstSurahCursor: Cursor? = null
- var lastSurahCursor: Cursor? = null
-
- try {
- firstSurahCursor = db.getAyahTimings(start.sura)
- firstSurahMap = populateArrayFromCursor(firstSurahCursor)!!
- lastSurahCursor = db.getAyahTimings(end.sura)
- lastSurahMap = populateArrayFromCursor(lastSurahCursor)
- } catch (sqlException: SQLException) {
- Timber.e(sqlException)
- } finally {
- closeCursor(firstSurahCursor)
- closeCursor(lastSurahCursor)
- }
-
- return ArrayList(
- listOf(firstSurahMap, lastSurahMap))
- }
-
- private fun populateArrayFromCursor(cursor: Cursor?): SparseIntArray {
- val sparseIntArray = SparseIntArray()
- if (cursor != null && cursor.moveToFirst()) {
- do {
- val ayah = cursor.getInt(1)
- val time = cursor.getInt(2)
- sparseIntArray.put(ayah, time)
- } while (cursor.moveToNext())
- }
- return sparseIntArray
- }
-
- private fun getSurahSegment(path: String, lowerCut: Int, upperCut: Int): String? {
- if (lowerCut == 0 && upperCut == 0) {
- return null
- }
- val tempAudioName = UUID.randomUUID().toString() + ".mp3"
- val destFile = File(audioCacheDirectory.path + File.separator + tempAudioName)
- val soundFile = arrayOfNulls(1)
- try {
- soundFile[0] = CheapSoundFile.create(path, null)
- val startTime = lowerCut.toFloat() / 1000
- val endTime = upperCut.toFloat() / 1000
- val samplesPerFrame = soundFile[0]?.samplesPerFrame
- val sampleRate = soundFile[0]?.sampleRate
- val avg = sampleRate?.div(samplesPerFrame!!)
- val startFrames = (startTime * avg!!).roundToInt()
- val endFrames = (endTime * avg).roundToInt()
- soundFile[0]?.WriteFile(destFile, startFrames, endFrames - startFrames)
- } catch (e: IOException) {
- e.printStackTrace()
- }
- return destFile.absolutePath
- }
-
- private fun getMergedAudioFromSegments(segments: ArrayList): String {
- var mergedAudioPath = segments[0]
- if (segments.size > 1) {
- for (i in 1 until segments.size) {
- mergedAudioPath = mergeAudios(mergedAudioPath, segments[i])!!
- audioCacheFilePaths.add(mergedAudioPath)
- }
- }
- return mergedAudioPath
- }
-
- private fun mergeAudios(path1: String, path2: String): String? {
- val tempAudioName = UUID.randomUUID().toString() + ".mp3"
- val destFile = File(audioCacheDirectory.path + File.separator + tempAudioName)
- try {
- val bufferedSink: BufferedSink = destFile.sink().buffer()
- bufferedSink.writeAll(File(path1).source())
- bufferedSink.writeAll(File(path2).source())
- bufferedSink.close()
- return destFile.path
- } catch (e2: FileNotFoundException) {
- e2.printStackTrace()
- } catch (e: IOException) {
- e.printStackTrace()
- }
- return null
- }
-
-
-}
diff --git a/feature/audioshare/build.gradle b/feature/audioshare/build.gradle
new file mode 100644
index 0000000000..e2d7298566
--- /dev/null
+++ b/feature/audioshare/build.gradle
@@ -0,0 +1,43 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+
+android {
+ compileSdkVersion deps.android.build.compileSdkVersion
+ defaultConfig {
+ minSdkVersion deps.android.build.minSdkVersion
+ targetSdkVersion deps.android.build.targetSdkVersion
+ }
+
+ buildTypes {
+ beta {
+ consumerProguardFiles 'proguard.cfg'
+ matchingFallbacks = ['debug']
+ }
+
+ release {
+ consumerProguardFiles 'proguard.cfg'
+ }
+ }
+}
+
+dependencies {
+ implementation project(path: ':common:audio')
+ implementation project(path: ':common:data')
+ implementation project(path: ':common:util')
+
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${coroutinesVersion}"
+
+ implementation "com.squareup.okio:okio:${okioVersion}"
+
+ implementation "com.squareup.moshi:moshi:${moshiVersion}"
+ kapt("com.squareup.moshi:moshi-kotlin-codegen:${moshiVersion}")
+
+ implementation "com.squareup.retrofit2:retrofit:${retrofitVersion}"
+
+ testImplementation "junit:junit:${junitVersion}"
+ testImplementation "com.google.truth:truth:${truthVersion}"
+
+ implementation "com.jakewharton.timber:timber:${timberVersion}"
+}
diff --git a/feature/audioshare/src/main/AndroidManifest.xml b/feature/audioshare/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..b34d4f3292
--- /dev/null
+++ b/feature/audioshare/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt b/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt
new file mode 100644
index 0000000000..a36bcebd7a
--- /dev/null
+++ b/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt
@@ -0,0 +1,302 @@
+package com.quran.labs.androidquran.feature.audioshare
+
+import android.content.Context
+import android.database.Cursor
+import android.database.SQLException
+import android.media.MediaMetadataRetriever
+import android.net.Uri
+import android.os.Environment
+import android.util.SparseIntArray
+import android.widget.Toast
+import com.quran.common.util.database.DatabaseUtils
+import com.quran.data.model.SuraAyah
+import com.quran.labs.androidquran.common.audio.model.QariItem
+import com.quran.labs.androidquran.common.audio.timing.SuraTimingDatabaseHandler
+import com.quran.labs.androidquran.feature.audioshare.soundfile.CheapSoundFile
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import okio.BufferedSink
+import okio.buffer
+import okio.sink
+import okio.source
+import timber.log.Timber
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.util.Locale
+import java.util.UUID
+import kotlin.math.roundToInt
+
+class AudioShareUtils {
+ fun createBlockingSharableAudioFile(
+ context: Context,
+ start: SuraAyah,
+ end: SuraAyah,
+ qari: QariItem,
+ urlFormat: String,
+ gaplessDatabase: String
+ ): String? {
+ return runBlocking {
+ createSharableAudioFile(context, start, end, qari, urlFormat, gaplessDatabase)
+ }
+ }
+
+ suspend fun createSharableAudioFile(
+ context: Context,
+ start: SuraAyah,
+ end: SuraAyah,
+ qari: QariItem,
+ urlFormat: String,
+ gaplessDatabase: String
+ ): String? {
+ assert(end >= start)
+
+ val audioCacheDirectory = File(
+ Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_MUSIC).path + File.separator + "quran_android_cache")
+
+ if (!audioCacheDirectory.exists()) {
+ if (!audioCacheDirectory.mkdirs()) {
+ Toast.makeText(context, "could not create directory", Toast.LENGTH_SHORT).show()
+ return null;
+ }
+ }
+
+ var sharablePath: String?
+ val audioCacheFilePaths = mutableListOf()
+ withContext(Dispatchers.IO) {
+ val mapArray = getTimingData(start, end, gaplessDatabase)
+
+ val startAyah = start.ayah
+ val endAyah = end.ayah
+ val startSurahTimingDataArray: SparseIntArray = mapArray[0]
+ val endSurahTimingDataArray: SparseIntArray = mapArray[1]
+
+ val isFirstAyahInSurah = startAyah == 1
+ val startTimeOfAyahAfterEndAyah = endSurahTimingDataArray[endAyah + 1]
+ val isLastAyahInSurah = startTimeOfAyahAfterEndAyah == 0
+
+ val startAyahTime = if (isFirstAyahInSurah) {
+ 0
+ } else {
+ startSurahTimingDataArray[startAyah]
+ }
+
+ val endAyahTime = if (isLastAyahInSurah) {
+ getSurahDuration(context, getSurahAudioPath(urlFormat, end.sura))
+ } else {
+ startTimeOfAyahAfterEndAyah
+ }
+
+ val startAndEndAyahAreInSameSurah = start.sura == end.sura
+
+ if (startAndEndAyahAreInSameSurah) {
+ val audioSegmentPath: String = getSurahSegment(
+ audioCacheDirectory, getSurahAudioPath(urlFormat, start.sura), startAyahTime, endAyahTime
+ )!!
+ audioCacheFilePaths.add(audioSegmentPath)
+ sharablePath = getRenamedSharableAudioFile(
+ qari,
+ start,
+ end,
+ audioSegmentPath,
+ audioCacheDirectory.toString(),
+ audioCacheFilePaths
+ )
+ audioCacheFilePaths.clear()
+ } else {
+ val segmentPaths = mutableListOf()
+ val endOfSurah = -1
+ val startOfSurah = 0
+ val startSegmentPath: String = getSurahSegmentPath(
+ context, audioCacheDirectory, urlFormat, start.sura, startAyahTime, endOfSurah
+ )
+ val lastSegmentPath: String = getSurahSegmentPath(
+ context, audioCacheDirectory , urlFormat, end.sura, startOfSurah, endAyahTime
+ )
+
+ for (surahIndex in start.sura..end.sura) {
+ val isTheFirstSurah = surahIndex == start.sura
+ val isMiddleSurah = surahIndex != start.sura && surahIndex != end.sura
+ if (isTheFirstSurah) {
+ segmentPaths.add(startSegmentPath)
+ audioCacheFilePaths.add(startSegmentPath)
+ continue
+ }
+ if (isMiddleSurah) {
+ segmentPaths.add(getSurahAudioPath(urlFormat, surahIndex))
+ continue
+ }
+ segmentPaths.add(lastSegmentPath)
+ audioCacheFilePaths.add(lastSegmentPath)
+ }
+
+ val audioSegmentsWereCreated = segmentPaths.isNotEmpty()
+
+ if (audioSegmentsWereCreated) {
+ val (sharableAudioFilePath, cacheUpdates) =
+ getMergedAudioFromSegments(audioCacheDirectory, segmentPaths)
+ audioCacheFilePaths.addAll(cacheUpdates)
+ sharablePath = getRenamedSharableAudioFile(
+ qari,
+ start,
+ end,
+ sharableAudioFilePath,
+ audioCacheDirectory.toString(),
+ audioCacheFilePaths
+ )
+ audioCacheFilePaths.clear()
+ } else {
+ sharablePath = null
+ }
+ }
+ }
+ return sharablePath
+ }
+
+ private fun getSurahSegmentPath(context: Context,
+ audioCacheDirectory: File,
+ urlFormat: String,
+ surah: Int,
+ startAyahTime: Int,
+ endAyahTime: Int
+ ): String {
+ var upperBoundTime = endAyahTime
+ val audioFilePath: String = getSurahAudioPath(urlFormat, surah)
+ val isFirstSegment = endAyahTime < 0
+ if (isFirstSegment) {
+ upperBoundTime = getSurahDuration(context, audioFilePath)
+ }
+ return getSurahSegment(audioCacheDirectory, audioFilePath, startAyahTime, upperBoundTime)!!
+ }
+
+ private fun getRenamedSharableAudioFile(
+ qari: QariItem,
+ start: SuraAyah,
+ end: SuraAyah,
+ audioSegmentPath: String,
+ audioCacheDirectory: String,
+ cachedPaths: List
+ ): String {
+ val newAudioFileName: String =
+ qari.path + "_" + start.sura + "-" + start.ayah + "_" + end.sura + "-" + end.ayah
+ val newAudioFilePath: String = audioCacheDirectory + File.separator + newAudioFileName + ".mp3"
+ File(audioSegmentPath).renameTo(File(newAudioFilePath))
+ cachedPaths
+ .filter { it != audioSegmentPath }
+ .onEach {
+ File(it).delete()
+ }
+ return newAudioFilePath
+ }
+
+ private fun getSurahDuration(context: Context, path: String): Int {
+ val mmr = MediaMetadataRetriever()
+ mmr.setDataSource(context, Uri.parse(path))
+ val durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
+ return durationStr!!.toInt()
+ }
+
+ private fun getSurahAudioPath(urlFormat: String, surah: Int): String {
+ return String.format(Locale.US, urlFormat, surah)
+ }
+
+ private fun getTimingData(start: SuraAyah, end: SuraAyah, gaplessDatabase: String): ArrayList {
+ val db: SuraTimingDatabaseHandler = SuraTimingDatabaseHandler.getDatabaseHandler(gaplessDatabase)
+ var firstSurahMap = SparseIntArray()
+ var lastSurahMap = SparseIntArray()
+ var firstSurahCursor: Cursor? = null
+ var lastSurahCursor: Cursor? = null
+
+ try {
+ firstSurahCursor = db.getAyahTimings(start.sura)
+ firstSurahMap = populateArrayFromCursor(firstSurahCursor)!!
+ lastSurahCursor = db.getAyahTimings(end.sura)
+ lastSurahMap = populateArrayFromCursor(lastSurahCursor)
+ } catch (sqlException: SQLException) {
+ Timber.e(sqlException)
+ } finally {
+ DatabaseUtils.closeCursor(firstSurahCursor)
+ DatabaseUtils.closeCursor(lastSurahCursor)
+ }
+
+ return ArrayList(
+ listOf(firstSurahMap, lastSurahMap))
+ }
+
+ private fun populateArrayFromCursor(cursor: Cursor?): SparseIntArray {
+ val sparseIntArray = SparseIntArray()
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ val ayah = cursor.getInt(1)
+ val time = cursor.getInt(2)
+ sparseIntArray.put(ayah, time)
+ } while (cursor.moveToNext())
+ }
+ return sparseIntArray
+ }
+
+ private fun getSurahSegment(
+ audioCacheDirectory: File,
+ path: String,
+ lowerCut: Int,
+ upperCut: Int
+ ): String? {
+ if (lowerCut == 0 && upperCut == 0) {
+ return null
+ }
+ val tempAudioName = UUID.randomUUID().toString() + ".mp3"
+ val destFile = File(audioCacheDirectory.path + File.separator + tempAudioName)
+ val soundFile = arrayOfNulls(1)
+ try {
+ soundFile[0] = CheapSoundFile.create(path, null)
+ val startTime = lowerCut.toFloat() / 1000
+ val endTime = upperCut.toFloat() / 1000
+ val samplesPerFrame = soundFile[0]?.samplesPerFrame
+ val sampleRate = soundFile[0]?.sampleRate
+ val avg = sampleRate?.div(samplesPerFrame!!)
+ val startFrames = (startTime * avg!!).roundToInt()
+ val endFrames = (endTime * avg).roundToInt()
+ soundFile[0]?.WriteFile(destFile, startFrames, endFrames - startFrames)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ return destFile.absolutePath
+ }
+
+ private fun getMergedAudioFromSegments(
+ audioCacheDirectory: File,
+ segments: List
+ ): Pair> {
+ var mergedAudioPath = segments[0]
+ val extraCacheFilePaths = mutableListOf()
+ if (segments.size > 1) {
+ for (i in 1 until segments.size) {
+ val path = mergeAudios(audioCacheDirectory, mergedAudioPath, segments[i])
+ if (path != null) {
+ mergedAudioPath = path
+ extraCacheFilePaths.add(mergedAudioPath)
+ }
+ }
+ }
+ return mergedAudioPath to extraCacheFilePaths
+ }
+
+ private fun mergeAudios(audioCacheDirectory: File, path1: String, path2: String): String? {
+ val tempAudioName = UUID.randomUUID().toString() + ".mp3"
+ val destFile = File(audioCacheDirectory.path + File.separator + tempAudioName)
+ try {
+ val bufferedSink: BufferedSink = destFile.sink().buffer()
+ bufferedSink.writeAll(File(path1).source())
+ bufferedSink.writeAll(File(path2).source())
+ bufferedSink.close()
+ return destFile.path
+ } catch (e2: FileNotFoundException) {
+ e2.printStackTrace()
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ return null
+ }
+}
diff --git a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapMP3.java b/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/soundfile/CheapMP3.java
similarity index 99%
rename from feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapMP3.java
rename to feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/soundfile/CheapMP3.java
index bedbc1af1c..2562eecfc5 100644
--- a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapMP3.java
+++ b/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/soundfile/CheapMP3.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.quran.labs.androidquran.feature.audio.util.soundfile;
+package com.quran.labs.androidquran.feature.audioshare.soundfile;
import java.io.File;
import java.io.FileInputStream;
diff --git a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapSoundFile.java b/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/soundfile/CheapSoundFile.java
similarity index 99%
rename from feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapSoundFile.java
rename to feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/soundfile/CheapSoundFile.java
index 9ea9dd27fa..9d34bda407 100644
--- a/feature/audio/src/main/java/com/quran/labs/androidquran/feature/audio/util/soundfile/CheapSoundFile.java
+++ b/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/soundfile/CheapSoundFile.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.quran.labs.androidquran.feature.audio.util.soundfile;
+package com.quran.labs.androidquran.feature.audioshare.soundfile;
import java.io.File;
import java.io.FileInputStream;
diff --git a/settings.gradle b/settings.gradle
index 1bd2ed2b15..4198ddcadb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -11,10 +11,12 @@ include ':common:reading'
include ':common:recitation'
include ':common:search'
include ':common:toolbar'
+include ':common:util'
include ':common:upgrade'
include ':common:ui:core'
include ':feature:analytics-noop'
include ':feature:audio'
+include ':feature:audioshare'
include ':feature:downloadmanager'
include ':feature:qarilist'
include ':feature:recitation'
From 6a19f9cbb4c19360c4177887eccee0d642cc05f8 Mon Sep 17 00:00:00 2001
From: Ahmed El-Helw
Date: Sun, 11 Sep 2022 16:59:55 +0400
Subject: [PATCH 15/18] Initial clean up to PagerActivity for audio share
---
.../presenter/audio/AudioPresenter.kt | 21 +------
.../labs/androidquran/ui/PagerActivity.java | 62 +++++++++----------
.../labs/androidquran/util/AudioUtils.kt | 10 ---
3 files changed, 30 insertions(+), 63 deletions(-)
diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt b/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt
index 1fde5313a1..0bb2d69385 100644
--- a/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt
@@ -4,13 +4,13 @@ import android.content.Context
import android.content.Intent
import com.quran.data.model.SuraAyah
import com.quran.labs.androidquran.R
+import com.quran.labs.androidquran.common.audio.model.AudioDownloadMetadata
import com.quran.labs.androidquran.common.audio.model.QariItem
import com.quran.labs.androidquran.dao.audio.AudioPathInfo
import com.quran.labs.androidquran.dao.audio.AudioRequest
import com.quran.labs.androidquran.data.QuranDisplayData
import com.quran.labs.androidquran.presenter.Presenter
import com.quran.labs.androidquran.service.QuranDownloadService
-import com.quran.labs.androidquran.common.audio.model.AudioDownloadMetadata
import com.quran.labs.androidquran.service.util.ServiceIntentHelper
import com.quran.labs.androidquran.ui.PagerActivity
import com.quran.labs.androidquran.util.AudioUtils
@@ -33,7 +33,7 @@ constructor(private val quranDisplayData: QuranDisplayData,
rangeRepeat: Int,
enforceRange: Boolean,
shouldStream: Boolean) {
- val audioPathInfo = getLocalAudioPathInfo(qari)
+ val audioPathInfo = audioUtil.getLocalAudioPathInfo(qari)
if (audioPathInfo != null) {
// override streaming if all the files are already downloaded
val stream = if (shouldStream) {
@@ -136,23 +136,6 @@ constructor(private val quranDisplayData: QuranDisplayData,
return ServiceIntentHelper.getAudioDownloadIntent(context, url, destination, title)
}
- private fun getLocalAudioPathInfo(qari: QariItem): AudioPathInfo? {
- pagerActivity?.let {
- val localPath = audioUtil.getLocalQariUrl(qari)
- if (localPath != null) {
- val databasePath = audioUtil.getQariDatabasePathIfGapless(qari)
- val urlFormat = if (databasePath.isNullOrEmpty()) {
- localPath + File.separator + "%d" + File.separator +
- "%d" + AudioUtils.AUDIO_EXTENSION
- } else {
- localPath + File.separator + "%03d" + AudioUtils.AUDIO_EXTENSION
- }
- return AudioPathInfo(urlFormat, localPath, databasePath)
- }
- }
- return null
- }
-
private fun haveAllFiles(audioPathInfo: AudioPathInfo, start: SuraAyah, end: SuraAyah): Boolean {
return audioUtil.haveAllFiles(audioPathInfo.urlFormat,
audioPathInfo.localDirectory, start, end, audioPathInfo.gaplessDatabase != null)
diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
index 5cd5fa5505..370e50518c 100644
--- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
+++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
@@ -260,11 +260,6 @@ public class PagerActivity extends AppCompatActivity implements
private final PagerHandler handler = new PagerHandler(this);
- private ArrayList audioCacheFilePaths = new ArrayList<>();
- private SuraAyah selectedStartSuraAyah = null;
- private SuraAyah selectedEndSuraAyah = null;
- private QariItem selectedQari = null;
-
private static class PagerHandler extends Handler {
private final WeakReference activity;
@@ -1902,59 +1897,58 @@ public void onError(@NonNull Throwable e) {
}
public void shareAyahAudio(SuraAyah start, SuraAyah end) {
- audioCacheFilePaths.clear();
-
- kotlin.Pair pair2 = getReorderedAyatPair(start, end);
- selectedStartSuraAyah = (SuraAyah) pair2.component1();
- selectedEndSuraAyah = (SuraAyah) pair2.component2();
+ final SuraAyah selectedStartSuraAyah;
+ final SuraAyah selectedEndSuraAyah;
+ if (start.compareTo(end) <= 0) {
+ selectedStartSuraAyah = start;
+ selectedEndSuraAyah = end;
+ } else {
+ selectedStartSuraAyah = end;
+ selectedEndSuraAyah = start;
+ Timber.e(new IllegalStateException("End isn't larger than the start: " + start + " to " + end));
+ }
- selectedQari = audioStatusBar.getAudioInfo();
+ final QariItem selectedQari = audioStatusBar.getAudioInfo();
AudioPathInfo audioPathInfo = audioUtils.getLocalAudioPathInfo(selectedQari);
assert audioPathInfo != null;
boolean gaplessDatabaseExists = audioPathInfo.getGaplessDatabase() != null;
if (gaplessDatabaseExists) {
- if (audioFilesExist(audioPathInfo)) {
+ if (audioFilesExist(audioPathInfo, selectedStartSuraAyah, selectedEndSuraAyah)) {
AudioShareUtils audioShareUtils = new AudioShareUtils();
- String path = audioShareUtils.createBlockingSharableAudioFile(this, selectedStartSuraAyah,
- selectedEndSuraAyah, selectedQari, audioPathInfo.getUrlFormat(),
- audioPathInfo.getGaplessDatabase());
- if(path != null && !path.isEmpty()){
+ String path = audioShareUtils.createBlockingSharableAudioFile(
+ this,
+ selectedStartSuraAyah,
+ selectedEndSuraAyah,
+ selectedQari,
+ audioPathInfo.getUrlFormat(),
+ audioPathInfo.getGaplessDatabase()
+ );
+
+ if (path != null && !path.isEmpty()){
shareAudioSegment(path);
- }else{
+ } else {
Toast.makeText(this, "could not share audio ayah", Toast.LENGTH_SHORT).show();
}
} else {
- requestDownload(audioPathInfo);
+ requestDownload(audioPathInfo, selectedQari, start, end);
}
}
}
- private kotlin.Pair getReorderedAyatPair(SuraAyah start, SuraAyah end) {
- kotlin.Pair pair;
- if (start.compareTo(end) <= 0) {
- pair = TuplesKt.to(start, end);
- } else {
- Timber.Forest.e(
- new IllegalStateException("End isn't larger than the start: " + start + " to " + end));
- pair = TuplesKt.to(end, start);
- }
- return pair;
- }
-
- private boolean audioFilesExist(AudioPathInfo audioPathInfo) {
+ private boolean audioFilesExist(AudioPathInfo audioPathInfo, SuraAyah start, SuraAyah end) {
return audioUtils.haveAllFiles(audioPathInfo.getUrlFormat(), audioPathInfo.getLocalDirectory(),
- selectedStartSuraAyah, selectedEndSuraAyah, true);
+ start, end, true);
}
private void shareAudioSegment(String path) {
shareUtil.shareAudioFileIntent(PagerActivity.this, new File(path));
}
- private void requestDownload(AudioPathInfo audioPathInfo) {
+ private void requestDownload(AudioPathInfo audioPathInfo, QariItem qari, SuraAyah start, SuraAyah end) {
AudioRequest audioRequest = new AudioRequest(
- selectedStartSuraAyah, selectedEndSuraAyah, selectedQari, 0, 0, true, false, audioPathInfo);
+ start, end, qari, 0, 0, true, false, audioPathInfo);
Intent downloadIntent = audioPresenter.getDownloadIntent(this, audioRequest);
if (downloadIntent != null) {
diff --git a/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt b/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
index 19aa4aa45d..6f594059d6 100644
--- a/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
@@ -83,16 +83,6 @@ class AudioUtils @Inject constructor(
return if (rootDirectory == null) null else rootDirectory + item.path
}
- fun getLocalQariUri(item: QariItem): String? {
- val rootDirectory = quranFileUtils.audioFileDirectory()
- return if (rootDirectory == null) null else
- rootDirectory + item.path + File.separator + if (item.isGapless) {
- "%03d$AUDIO_EXTENSION"
- } else {
- "%d" + File.separator + "%d" + AUDIO_EXTENSION
- }
- }
-
fun getQariDatabasePathIfGapless(item: QariItem): String? {
var databaseName = item.databaseName
if (databaseName != null) {
From 1612b9cf9ee16641d8480b8e9a9e81b0c88880a0 Mon Sep 17 00:00:00 2001
From: Ahmed El-Helw
Date: Sun, 11 Sep 2022 17:42:21 +0400
Subject: [PATCH 16/18] Move some methods into audio file utilities
---
.../androidquran/dao/audio/AudioRequest.kt | 4 +-
.../presenter/audio/AudioPresenter.kt | 6 ++-
.../labs/androidquran/ui/PagerActivity.java | 24 +++------
.../labs/androidquran/util/AudioUtils.kt | 36 +-------------
.../androidquran/worker/AudioUpdateWorker.kt | 22 ++++++---
.../cache/command/GaplessAudioInfoCommand.kt | 6 +--
.../cache/command/GappedAudioInfoCommand.kt | 4 +-
.../common/audio/model}/AudioPathInfo.kt | 2 +-
.../common/audio/util/AudioFileTools.kt | 18 +++++++
.../common/audio/util/AudioFileUtil.kt | 49 +++++++++++++++----
10 files changed, 93 insertions(+), 78 deletions(-)
rename {app/src/main/java/com/quran/labs/androidquran/dao/audio => common/audio/src/main/java/com/quran/labs/androidquran/common/audio/model}/AudioPathInfo.kt (81%)
create mode 100644 common/audio/src/main/java/com/quran/labs/androidquran/common/audio/util/AudioFileTools.kt
diff --git a/app/src/main/java/com/quran/labs/androidquran/dao/audio/AudioRequest.kt b/app/src/main/java/com/quran/labs/androidquran/dao/audio/AudioRequest.kt
index d24f389fff..02a73133b3 100644
--- a/app/src/main/java/com/quran/labs/androidquran/dao/audio/AudioRequest.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/dao/audio/AudioRequest.kt
@@ -3,6 +3,7 @@ package com.quran.labs.androidquran.dao.audio
import android.os.Parcelable
import com.quran.labs.androidquran.common.audio.model.QariItem
import com.quran.data.model.SuraAyah
+import com.quran.labs.androidquran.common.audio.model.AudioPathInfo
import kotlinx.parcelize.Parcelize
@Parcelize
@@ -13,7 +14,8 @@ data class AudioRequest(val start: SuraAyah,
val rangeRepeatInfo: Int = 0,
val enforceBounds: Boolean,
val shouldStream: Boolean,
- val audioPathInfo: AudioPathInfo) : Parcelable {
+ val audioPathInfo: AudioPathInfo
+) : Parcelable {
fun isGapless() = qari.isGapless
fun needsIsti3athaAudio() =
!isGapless() || audioPathInfo.gaplessDatabase?.contains("minshawi_murattal") ?: false
diff --git a/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt b/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt
index 0bb2d69385..f42fb30e01 100644
--- a/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/presenter/audio/AudioPresenter.kt
@@ -5,8 +5,9 @@ import android.content.Intent
import com.quran.data.model.SuraAyah
import com.quran.labs.androidquran.R
import com.quran.labs.androidquran.common.audio.model.AudioDownloadMetadata
+import com.quran.labs.androidquran.common.audio.model.AudioPathInfo
import com.quran.labs.androidquran.common.audio.model.QariItem
-import com.quran.labs.androidquran.dao.audio.AudioPathInfo
+import com.quran.labs.androidquran.common.audio.util.AudioFileUtil
import com.quran.labs.androidquran.dao.audio.AudioRequest
import com.quran.labs.androidquran.data.QuranDisplayData
import com.quran.labs.androidquran.presenter.Presenter
@@ -22,6 +23,7 @@ import javax.inject.Inject
class AudioPresenter @Inject
constructor(private val quranDisplayData: QuranDisplayData,
private val audioUtil: AudioUtils,
+ private val audioFileUtil: AudioFileUtil,
private val quranFileUtils: QuranFileUtils) : Presenter {
private var pagerActivity: PagerActivity? = null
private var lastAudioRequest: AudioRequest? = null
@@ -33,7 +35,7 @@ constructor(private val quranDisplayData: QuranDisplayData,
rangeRepeat: Int,
enforceRange: Boolean,
shouldStream: Boolean) {
- val audioPathInfo = audioUtil.getLocalAudioPathInfo(qari)
+ val audioPathInfo = audioFileUtil.getLocalAudioPathInfo(qari)
if (audioPathInfo != null) {
// override streaming if all the files are already downloaded
val stream = if (shouldStream) {
diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
index 370e50518c..8ba6af7a6a 100644
--- a/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
+++ b/app/src/main/java/com/quran/labs/androidquran/ui/PagerActivity.java
@@ -71,7 +71,8 @@
import com.quran.labs.androidquran.common.LocalTranslationDisplaySort;
import com.quran.labs.androidquran.common.QuranAyahInfo;
import com.quran.labs.androidquran.common.audio.model.QariItem;
-import com.quran.labs.androidquran.dao.audio.AudioPathInfo;
+import com.quran.labs.androidquran.common.audio.model.AudioPathInfo;
+import com.quran.labs.androidquran.common.audio.util.AudioFileUtil;
import com.quran.labs.androidquran.dao.audio.AudioRequest;
import com.quran.labs.androidquran.data.Constants;
import com.quran.labs.androidquran.data.QuranDataProvider;
@@ -148,7 +149,6 @@
import io.reactivex.rxjava3.observers.DisposableObserver;
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
import io.reactivex.rxjava3.schedulers.Schedulers;
-import kotlin.TuplesKt;
import timber.log.Timber;
/**
@@ -240,6 +240,7 @@ public class PagerActivity extends AppCompatActivity implements
@Inject QuranAppUtils quranAppUtils;
@Inject ShareUtil shareUtil;
@Inject AudioUtils audioUtils;
+ @Inject AudioFileUtil audioFileUtil;
@Inject QuranDisplayData quranDisplayData;
@Inject QuranInfo quranInfo;
@Inject QuranFileUtils quranFileUtils;
@@ -1897,30 +1898,19 @@ public void onError(@NonNull Throwable e) {
}
public void shareAyahAudio(SuraAyah start, SuraAyah end) {
- final SuraAyah selectedStartSuraAyah;
- final SuraAyah selectedEndSuraAyah;
- if (start.compareTo(end) <= 0) {
- selectedStartSuraAyah = start;
- selectedEndSuraAyah = end;
- } else {
- selectedStartSuraAyah = end;
- selectedEndSuraAyah = start;
- Timber.e(new IllegalStateException("End isn't larger than the start: " + start + " to " + end));
- }
-
final QariItem selectedQari = audioStatusBar.getAudioInfo();
- AudioPathInfo audioPathInfo = audioUtils.getLocalAudioPathInfo(selectedQari);
+ AudioPathInfo audioPathInfo = audioFileUtil.getLocalAudioPathInfo(selectedQari);
assert audioPathInfo != null;
boolean gaplessDatabaseExists = audioPathInfo.getGaplessDatabase() != null;
if (gaplessDatabaseExists) {
- if (audioFilesExist(audioPathInfo, selectedStartSuraAyah, selectedEndSuraAyah)) {
+ if (audioFilesExist(audioPathInfo, start, end)) {
AudioShareUtils audioShareUtils = new AudioShareUtils();
String path = audioShareUtils.createBlockingSharableAudioFile(
this,
- selectedStartSuraAyah,
- selectedEndSuraAyah,
+ start,
+ end,
selectedQari,
audioPathInfo.getUrlFormat(),
audioPathInfo.getGaplessDatabase()
diff --git a/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt b/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
index 6f594059d6..ab6d7d4485 100644
--- a/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/util/AudioUtils.kt
@@ -8,12 +8,11 @@ import com.quran.data.model.SuraAyah
import com.quran.data.model.audio.Qari
import com.quran.labs.androidquran.common.audio.model.QariItem
import com.quran.labs.androidquran.common.audio.util.QariUtil
-import com.quran.labs.androidquran.dao.audio.AudioPathInfo
import com.quran.labs.androidquran.service.AudioService
+import timber.log.Timber
import java.io.File
import java.util.Locale
import javax.inject.Inject
-import timber.log.Timber
class AudioUtils @Inject constructor(
private val quranInfo: QuranInfo,
@@ -78,22 +77,6 @@ class AudioUtils @Inject constructor(
}
}
- fun getLocalQariUrl(item: QariItem): String? {
- val rootDirectory = quranFileUtils.audioFileDirectory()
- return if (rootDirectory == null) null else rootDirectory + item.path
- }
-
- fun getQariDatabasePathIfGapless(item: QariItem): String? {
- var databaseName = item.databaseName
- if (databaseName != null) {
- val path = getLocalQariUrl(item)
- if (path != null) {
- databaseName = path + File.separator + databaseName + DB_EXTENSION
- }
- }
- return databaseName
- }
-
fun getLastAyahToPlay(
startAyah: SuraAyah,
currentPage: Int,
@@ -281,25 +264,8 @@ class AudioUtils @Inject constructor(
}
}
- fun getLocalAudioPathInfo(qari: QariItem): AudioPathInfo? {
- val localPath = getLocalQariUrl(qari)
- if (localPath != null) {
- val databasePath = getQariDatabasePathIfGapless(qari)
- val urlFormat = if (databasePath.isNullOrEmpty()) {
- localPath + File.separator + "%d" + File.separator +
- "%d" + AUDIO_EXTENSION
- } else {
- localPath + File.separator + "%03d" + AUDIO_EXTENSION
- }
- return AudioPathInfo(urlFormat, localPath, databasePath)
- }
- return null
- }
-
companion object {
const val ZIP_EXTENSION = ".zip"
const val AUDIO_EXTENSION = ".mp3"
-
- private const val DB_EXTENSION = ".db"
}
}
diff --git a/app/src/main/java/com/quran/labs/androidquran/worker/AudioUpdateWorker.kt b/app/src/main/java/com/quran/labs/androidquran/worker/AudioUpdateWorker.kt
index 06111025f1..116c7fc325 100644
--- a/app/src/main/java/com/quran/labs/androidquran/worker/AudioUpdateWorker.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/worker/AudioUpdateWorker.kt
@@ -8,10 +8,11 @@ import androidx.work.CoroutineWorker
import androidx.work.ListenableWorker
import androidx.work.WorkerParameters
import com.quran.labs.androidquran.R
+import com.quran.labs.androidquran.common.audio.timing.SuraTimingDatabaseHandler
+import com.quran.labs.androidquran.common.audio.util.AudioFileUtil
import com.quran.labs.androidquran.core.worker.WorkerTaskFactory
import com.quran.labs.androidquran.data.Constants
import com.quran.labs.androidquran.database.AudioDatabaseVersionChecker
-import com.quran.labs.androidquran.common.audio.timing.SuraTimingDatabaseHandler
import com.quran.labs.androidquran.feature.audio.AudioUpdater
import com.quran.labs.androidquran.feature.audio.api.AudioUpdateService
import com.quran.labs.androidquran.feature.audio.util.AudioFileCheckerImpl
@@ -31,7 +32,8 @@ class AudioUpdateWorker(
private val audioUpdateService: AudioUpdateService,
private val audioUtils: AudioUtils,
private val quranFileUtils: QuranFileUtils,
- private val quranSettings: QuranSettings
+ private val quranSettings: QuranSettings,
+ private val audioFileUtil: AudioFileUtil
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result = coroutineScope {
@@ -54,14 +56,14 @@ class AudioUpdateWorker(
localFilesToDelete.forEach { localUpdate ->
if (localUpdate.needsDatabaseUpgrade) {
// delete the database
- val dbPath = audioUtils.getQariDatabasePathIfGapless(localUpdate.qari)
+ val dbPath = audioFileUtil.getQariDatabasePathIfGapless(localUpdate.qari)
dbPath?.let { SuraTimingDatabaseHandler.clearDatabaseHandlerIfExists(it) }
Timber.d("would remove %s", dbPath)
File(dbPath).delete()
}
val qari = localUpdate.qari
- val path = audioUtils.getLocalQariUrl(qari)
+ val path = audioFileUtil.getLocalQariUrl(qari)
localUpdate.files.forEach {
// delete the file
val filePath = if (qari.isGapless) {
@@ -116,15 +118,21 @@ class AudioUpdateWorker(
private val audioUpdateService: AudioUpdateService,
private val audioUtils: AudioUtils,
private val quranFileUtils: QuranFileUtils,
- private val quranSettings: QuranSettings
+ private val quranSettings: QuranSettings,
+ private val audioFileUtil: AudioFileUtil
) : WorkerTaskFactory {
override fun makeWorker(
appContext: Context,
workerParameters: WorkerParameters
): ListenableWorker {
return AudioUpdateWorker(
- appContext, workerParameters, audioUpdateService, audioUtils, quranFileUtils,
- quranSettings
+ appContext,
+ workerParameters,
+ audioUpdateService,
+ audioUtils,
+ quranFileUtils,
+ quranSettings,
+ audioFileUtil
)
}
}
diff --git a/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/cache/command/GaplessAudioInfoCommand.kt b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/cache/command/GaplessAudioInfoCommand.kt
index 248f36de6b..1be405e6cf 100644
--- a/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/cache/command/GaplessAudioInfoCommand.kt
+++ b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/cache/command/GaplessAudioInfoCommand.kt
@@ -1,6 +1,6 @@
package com.quran.labs.androidquran.common.audio.cache.command
-import com.quran.labs.androidquran.common.audio.util.AudioFileUtil
+import com.quran.labs.androidquran.common.audio.util.AudioFileTools
import okio.FileSystem
import okio.Path
import javax.inject.Inject
@@ -12,7 +12,7 @@ class GaplessAudioInfoCommand @Inject constructor(private val fileSystem: FileSy
}
private fun fullGaplessDownloads(path: Path): List {
- val paths = AudioFileUtil.filesMatchingSuffixWithSuffixRemoved(fileSystem, path, ".mp3")
+ val paths = AudioFileTools.filesMatchingSuffixWithSuffixRemoved(fileSystem, path, ".mp3")
return paths
.filter { it.length == 3 }
.mapNotNull { it.toIntOrNull() }
@@ -20,7 +20,7 @@ class GaplessAudioInfoCommand @Inject constructor(private val fileSystem: FileSy
}
private fun partialGaplessDownloads(path: Path): List {
- val paths = AudioFileUtil.filesMatchingSuffixWithSuffixRemoved(fileSystem, path, ".mp3.part")
+ val paths = AudioFileTools.filesMatchingSuffixWithSuffixRemoved(fileSystem, path, ".mp3.part")
return paths
.filter { it.length == 3 }
.mapNotNull { it.toIntOrNull() }
diff --git a/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/cache/command/GappedAudioInfoCommand.kt b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/cache/command/GappedAudioInfoCommand.kt
index 1cad0ca264..fc51123cd3 100644
--- a/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/cache/command/GappedAudioInfoCommand.kt
+++ b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/cache/command/GappedAudioInfoCommand.kt
@@ -2,7 +2,7 @@ package com.quran.labs.androidquran.common.audio.cache.command
import com.quran.data.core.QuranInfo
import com.quran.labs.androidquran.common.audio.model.PartiallyDownloadedSura
-import com.quran.labs.androidquran.common.audio.util.AudioFileUtil
+import com.quran.labs.androidquran.common.audio.util.AudioFileTools
import okio.FileSystem
import okio.Path
import javax.inject.Inject
@@ -17,7 +17,7 @@ class GappedAudioInfoCommand @Inject constructor(
.filter { it.name.toIntOrNull() in 1..114 }
.associate { directory ->
val gappedDownloads =
- AudioFileUtil.filesMatchingSuffixWithSuffixRemoved(fileSystem, directory, ".mp3")
+ AudioFileTools.filesMatchingSuffixWithSuffixRemoved(fileSystem, directory, ".mp3")
.mapNotNull { it.toIntOrNull() }
.filter { it in 1..286 }
directory.toFile().nameWithoutExtension.toInt() to gappedDownloads
diff --git a/app/src/main/java/com/quran/labs/androidquran/dao/audio/AudioPathInfo.kt b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/model/AudioPathInfo.kt
similarity index 81%
rename from app/src/main/java/com/quran/labs/androidquran/dao/audio/AudioPathInfo.kt
rename to common/audio/src/main/java/com/quran/labs/androidquran/common/audio/model/AudioPathInfo.kt
index 7a1cd0e563..ff9f483d49 100644
--- a/app/src/main/java/com/quran/labs/androidquran/dao/audio/AudioPathInfo.kt
+++ b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/model/AudioPathInfo.kt
@@ -1,4 +1,4 @@
-package com.quran.labs.androidquran.dao.audio
+package com.quran.labs.androidquran.common.audio.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
diff --git a/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/util/AudioFileTools.kt b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/util/AudioFileTools.kt
new file mode 100644
index 0000000000..7a5643decb
--- /dev/null
+++ b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/util/AudioFileTools.kt
@@ -0,0 +1,18 @@
+package com.quran.labs.androidquran.common.audio.util
+
+import okio.FileSystem
+import okio.Path
+
+object AudioFileTools {
+
+ fun filesMatchingSuffixWithSuffixRemoved(fileSystem: FileSystem, path: Path, suffix: String): List {
+ return fileNamesMatchingSuffix(fileSystem, path, suffix)
+ .map { it.name.removeSuffix(suffix) }
+ }
+
+ private fun fileNamesMatchingSuffix(fileSystem: FileSystem, path: Path, suffix: String): List {
+ return fileSystem.listOrNull(path)
+ ?.filter { it.name.endsWith(suffix) }
+ ?: emptyList()
+ }
+}
diff --git a/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/util/AudioFileUtil.kt b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/util/AudioFileUtil.kt
index 1ee5f44793..5a40fb24f2 100644
--- a/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/util/AudioFileUtil.kt
+++ b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/util/AudioFileUtil.kt
@@ -1,18 +1,47 @@
package com.quran.labs.androidquran.common.audio.util
-import okio.FileSystem
-import okio.Path
+import com.quran.data.core.QuranFileManager
+import com.quran.labs.androidquran.common.audio.model.AudioPathInfo
+import com.quran.labs.androidquran.common.audio.model.QariItem
+import java.io.File
+import javax.inject.Inject
-object AudioFileUtil {
+class AudioFileUtil @Inject constructor(private val quranFileManager: QuranFileManager) {
- fun filesMatchingSuffixWithSuffixRemoved(fileSystem: FileSystem, path: Path, suffix: String): List {
- return fileNamesMatchingSuffix(fileSystem, path, suffix)
- .map { it.name.removeSuffix(suffix) }
+ fun getLocalQariUrl(item: QariItem): String? {
+ val rootDirectory = quranFileManager.audioFileDirectory()
+ return if (rootDirectory == null) null else rootDirectory + item.path
}
- private fun fileNamesMatchingSuffix(fileSystem: FileSystem, path: Path, suffix: String): List {
- return fileSystem.listOrNull(path)
- ?.filter { it.name.endsWith(suffix) }
- ?: emptyList()
+ fun getQariDatabasePathIfGapless(item: QariItem): String? {
+ var databaseName = item.databaseName
+ if (databaseName != null) {
+ val path = getLocalQariUrl(item)
+ if (path != null) {
+ databaseName = path + File.separator + databaseName + DB_EXTENSION
+ }
+ }
+ return databaseName
+ }
+
+ fun getLocalAudioPathInfo(qari: QariItem): AudioPathInfo? {
+ val localPath = getLocalQariUrl(qari)
+ if (localPath != null) {
+ val databasePath = getQariDatabasePathIfGapless(qari)
+ val urlFormat = if (databasePath.isNullOrEmpty()) {
+ localPath + File.separator + "%d" + File.separator +
+ "%d" + AUDIO_EXTENSION
+ } else {
+ localPath + File.separator + "%03d" + AUDIO_EXTENSION
+ }
+ return AudioPathInfo(urlFormat, localPath, databasePath)
+ }
+ return null
+ }
+
+ companion object {
+ const val AUDIO_EXTENSION = ".mp3"
+
+ private const val DB_EXTENSION = ".db"
}
}
From ddbef2552d3c096dffacad79faa85e2f713e02e9 Mon Sep 17 00:00:00 2001
From: Ahmed El-Helw
Date: Sun, 11 Sep 2022 23:04:09 +0400
Subject: [PATCH 17/18] Use normal application cache directory
---
app/src/main/AndroidManifest.xml | 4 +---
.../androidquran/feature/audioshare/AudioShareUtils.kt | 9 +++------
2 files changed, 4 insertions(+), 9 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9bc9d7eee7..22a51739d9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -99,11 +99,9 @@
android:authorities="@string/file_authority"
android:grantUriPermissions="true"
android:exported="false">
- tools:replace="android:authorities">
+ android:resource="@xml/file_paths" />
diff --git a/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt b/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt
index a36bcebd7a..103b8c84c6 100644
--- a/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt
+++ b/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt
@@ -52,14 +52,11 @@ class AudioShareUtils {
): String? {
assert(end >= start)
- val audioCacheDirectory = File(
- Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_MUSIC).path + File.separator + "quran_android_cache")
-
+ val audioCacheDirectory = context.cacheDir
if (!audioCacheDirectory.exists()) {
if (!audioCacheDirectory.mkdirs()) {
Toast.makeText(context, "could not create directory", Toast.LENGTH_SHORT).show()
- return null;
+ return null
}
}
@@ -211,7 +208,7 @@ class AudioShareUtils {
try {
firstSurahCursor = db.getAyahTimings(start.sura)
- firstSurahMap = populateArrayFromCursor(firstSurahCursor)!!
+ firstSurahMap = populateArrayFromCursor(firstSurahCursor)
lastSurahCursor = db.getAyahTimings(end.sura)
lastSurahMap = populateArrayFromCursor(lastSurahCursor)
} catch (sqlException: SQLException) {
From 562aaba7fe8942fceee6d7b6e3d73e4f3aaba8ab Mon Sep 17 00:00:00 2001
From: Ahmed El-Helw
Date: Mon, 12 Sep 2022 00:15:36 +0400
Subject: [PATCH 18/18] Additional cleanup to AudioShareUtils
---
.../labs/androidquran/service/AudioService.kt | 22 +--
.../audio/timing/SuraTimingDatabaseHandler.kt | 25 ++-
.../feature/audioshare/AudioShareUtils.kt | 177 ++++++++----------
3 files changed, 103 insertions(+), 121 deletions(-)
diff --git a/app/src/main/java/com/quran/labs/androidquran/service/AudioService.kt b/app/src/main/java/com/quran/labs/androidquran/service/AudioService.kt
index d1d9c514d0..cd49367b2e 100644
--- a/app/src/main/java/com/quran/labs/androidquran/service/AudioService.kt
+++ b/app/src/main/java/com/quran/labs/androidquran/service/AudioService.kt
@@ -410,27 +410,7 @@ class AudioService : Service(), OnCompletionListener, OnPreparedListener,
timingDisposable?.dispose()
timingDisposable = Single.fromCallable {
val db = getDatabaseHandler(databasePath)
-
- val map = SparseIntArray()
- var cursor: Cursor? = null
- try {
- cursor = db.getAyahTimings(sura)
- Timber.d("got cursor of data")
- if (cursor != null && cursor.moveToFirst()) {
- do {
- val ayah = cursor.getInt(1)
- val time = cursor.getInt(2)
- map.put(ayah, time)
- } while (cursor.moveToNext())
- }
- } catch (se: SQLException) {
- // don't crash the app if the database is corrupt
- Timber.e(se)
- } finally {
- DatabaseUtils.closeCursor(cursor)
- }
-
- map
+ db.getAyahTimings(sura)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
diff --git a/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/timing/SuraTimingDatabaseHandler.kt b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/timing/SuraTimingDatabaseHandler.kt
index 76e732b385..b56090f113 100644
--- a/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/timing/SuraTimingDatabaseHandler.kt
+++ b/common/audio/src/main/java/com/quran/labs/androidquran/common/audio/timing/SuraTimingDatabaseHandler.kt
@@ -5,6 +5,7 @@ import android.database.DefaultDatabaseErrorHandler
import android.database.SQLException
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteDatabaseCorruptException
+import android.util.SparseIntArray
import com.quran.common.util.database.DatabaseUtils
import timber.log.Timber
import java.io.File
@@ -72,7 +73,7 @@ class SuraTimingDatabaseHandler private constructor(path: String) {
private fun validDatabase(): Boolean = database?.isOpen ?: false
- fun getAyahTimings(sura: Int): Cursor? {
+ private fun getAyahTimingsCursor(sura: Int): Cursor? {
if (!validDatabase()) return null
return try {
@@ -89,6 +90,28 @@ class SuraTimingDatabaseHandler private constructor(path: String) {
}
}
+ fun getAyahTimings(sura: Int): SparseIntArray {
+ val map = SparseIntArray()
+
+ var cursor: Cursor? = null
+ try {
+ cursor = getAyahTimingsCursor(sura)
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ val ayah = cursor.getInt(1)
+ val time = cursor.getInt(2)
+ map.put(ayah, time)
+ } while (cursor.moveToNext())
+ }
+ } catch (exception: SQLException) {
+ Timber.e(exception)
+ } finally {
+ DatabaseUtils.closeCursor(cursor)
+ }
+
+ return map
+ }
+
fun getVersion(): Int {
if (!validDatabase()) {
return -1
diff --git a/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt b/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt
index 103b8c84c6..a1d170fb6c 100644
--- a/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt
+++ b/feature/audioshare/src/main/java/com/quran/labs/androidquran/feature/audioshare/AudioShareUtils.kt
@@ -1,14 +1,9 @@
package com.quran.labs.androidquran.feature.audioshare
import android.content.Context
-import android.database.Cursor
-import android.database.SQLException
import android.media.MediaMetadataRetriever
import android.net.Uri
-import android.os.Environment
import android.util.SparseIntArray
-import android.widget.Toast
-import com.quran.common.util.database.DatabaseUtils
import com.quran.data.model.SuraAyah
import com.quran.labs.androidquran.common.audio.model.QariItem
import com.quran.labs.androidquran.common.audio.timing.SuraTimingDatabaseHandler
@@ -20,7 +15,6 @@ import okio.BufferedSink
import okio.buffer
import okio.sink
import okio.source
-import timber.log.Timber
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
@@ -53,35 +47,32 @@ class AudioShareUtils {
assert(end >= start)
val audioCacheDirectory = context.cacheDir
- if (!audioCacheDirectory.exists()) {
- if (!audioCacheDirectory.mkdirs()) {
- Toast.makeText(context, "could not create directory", Toast.LENGTH_SHORT).show()
- return null
- }
- }
var sharablePath: String?
val audioCacheFilePaths = mutableListOf()
withContext(Dispatchers.IO) {
- val mapArray = getTimingData(start, end, gaplessDatabase)
+ val (startSurahTimingData, endSurahTimingData) = getTimingData(start, end, gaplessDatabase)
val startAyah = start.ayah
val endAyah = end.ayah
- val startSurahTimingDataArray: SparseIntArray = mapArray[0]
- val endSurahTimingDataArray: SparseIntArray = mapArray[1]
val isFirstAyahInSurah = startAyah == 1
- val startTimeOfAyahAfterEndAyah = endSurahTimingDataArray[endAyah + 1]
+ val startTimeOfAyahAfterEndAyah = endSurahTimingData[endAyah + 1]
val isLastAyahInSurah = startTimeOfAyahAfterEndAyah == 0
val startAyahTime = if (isFirstAyahInSurah) {
0
} else {
- startSurahTimingDataArray[startAyah]
+ startSurahTimingData[startAyah]
}
val endAyahTime = if (isLastAyahInSurah) {
- getSurahDuration(context, getSurahAudioPath(urlFormat, end.sura))
+ val endMarker = endSurahTimingData.get(999, -1)
+ if (endMarker > 0) {
+ endMarker
+ } else {
+ getSurahDuration(context, getSurahAudioPath(urlFormat, end.sura))
+ }
} else {
startTimeOfAyahAfterEndAyah
}
@@ -89,61 +80,68 @@ class AudioShareUtils {
val startAndEndAyahAreInSameSurah = start.sura == end.sura
if (startAndEndAyahAreInSameSurah) {
- val audioSegmentPath: String = getSurahSegment(
+ val audioSegmentPath: String? = getSurahSegment(
audioCacheDirectory, getSurahAudioPath(urlFormat, start.sura), startAyahTime, endAyahTime
- )!!
- audioCacheFilePaths.add(audioSegmentPath)
- sharablePath = getRenamedSharableAudioFile(
- qari,
- start,
- end,
- audioSegmentPath,
- audioCacheDirectory.toString(),
- audioCacheFilePaths
)
+
+ sharablePath = if (audioSegmentPath != null) {
+ audioCacheFilePaths.add(audioSegmentPath)
+ getRenamedSharableAudioFile(
+ qari,
+ start,
+ end,
+ audioSegmentPath,
+ audioCacheDirectory.toString(),
+ audioCacheFilePaths
+ )
+ } else {
+ null
+ }
audioCacheFilePaths.clear()
} else {
val segmentPaths = mutableListOf()
val endOfSurah = -1
val startOfSurah = 0
- val startSegmentPath: String = getSurahSegmentPath(
+ val startSegmentPath: String? = getSurahSegmentPath(
context, audioCacheDirectory, urlFormat, start.sura, startAyahTime, endOfSurah
)
- val lastSegmentPath: String = getSurahSegmentPath(
- context, audioCacheDirectory , urlFormat, end.sura, startOfSurah, endAyahTime
+ val lastSegmentPath: String? = getSurahSegmentPath(
+ context, audioCacheDirectory, urlFormat, end.sura, startOfSurah, endAyahTime
)
- for (surahIndex in start.sura..end.sura) {
- val isTheFirstSurah = surahIndex == start.sura
- val isMiddleSurah = surahIndex != start.sura && surahIndex != end.sura
- if (isTheFirstSurah) {
- segmentPaths.add(startSegmentPath)
- audioCacheFilePaths.add(startSegmentPath)
- continue
- }
- if (isMiddleSurah) {
- segmentPaths.add(getSurahAudioPath(urlFormat, surahIndex))
- continue
+ if (startSegmentPath != null && lastSegmentPath != null) {
+ for (surahIndex in start.sura..end.sura) {
+ val isTheFirstSurah = surahIndex == start.sura
+ val isMiddleSurah = surahIndex != start.sura && surahIndex != end.sura
+ if (isTheFirstSurah) {
+ segmentPaths.add(startSegmentPath)
+ audioCacheFilePaths.add(startSegmentPath)
+ } else if (isMiddleSurah) {
+ segmentPaths.add(getSurahAudioPath(urlFormat, surahIndex))
+ } else {
+ segmentPaths.add(lastSegmentPath)
+ audioCacheFilePaths.add(lastSegmentPath)
+ }
}
- segmentPaths.add(lastSegmentPath)
- audioCacheFilePaths.add(lastSegmentPath)
- }
- val audioSegmentsWereCreated = segmentPaths.isNotEmpty()
+ val audioSegmentsWereCreated = segmentPaths.isNotEmpty()
- if (audioSegmentsWereCreated) {
- val (sharableAudioFilePath, cacheUpdates) =
- getMergedAudioFromSegments(audioCacheDirectory, segmentPaths)
- audioCacheFilePaths.addAll(cacheUpdates)
- sharablePath = getRenamedSharableAudioFile(
- qari,
- start,
- end,
- sharableAudioFilePath,
- audioCacheDirectory.toString(),
- audioCacheFilePaths
- )
- audioCacheFilePaths.clear()
+ if (audioSegmentsWereCreated) {
+ val (sharableAudioFilePath, cacheUpdates) =
+ getMergedAudioFromSegments(audioCacheDirectory, segmentPaths)
+ audioCacheFilePaths.addAll(cacheUpdates)
+ sharablePath = getRenamedSharableAudioFile(
+ qari,
+ start,
+ end,
+ sharableAudioFilePath,
+ audioCacheDirectory.toString(),
+ audioCacheFilePaths
+ )
+ audioCacheFilePaths.clear()
+ } else {
+ sharablePath = null
+ }
} else {
sharablePath = null
}
@@ -158,14 +156,14 @@ class AudioShareUtils {
surah: Int,
startAyahTime: Int,
endAyahTime: Int
- ): String {
+ ): String? {
var upperBoundTime = endAyahTime
val audioFilePath: String = getSurahAudioPath(urlFormat, surah)
val isFirstSegment = endAyahTime < 0
if (isFirstSegment) {
upperBoundTime = getSurahDuration(context, audioFilePath)
}
- return getSurahSegment(audioCacheDirectory, audioFilePath, startAyahTime, upperBoundTime)!!
+ return getSurahSegment(audioCacheDirectory, audioFilePath, startAyahTime, upperBoundTime)
}
private fun getRenamedSharableAudioFile(
@@ -199,39 +197,21 @@ class AudioShareUtils {
return String.format(Locale.US, urlFormat, surah)
}
- private fun getTimingData(start: SuraAyah, end: SuraAyah, gaplessDatabase: String): ArrayList {
- val db: SuraTimingDatabaseHandler = SuraTimingDatabaseHandler.getDatabaseHandler(gaplessDatabase)
- var firstSurahMap = SparseIntArray()
- var lastSurahMap = SparseIntArray()
- var firstSurahCursor: Cursor? = null
- var lastSurahCursor: Cursor? = null
-
- try {
- firstSurahCursor = db.getAyahTimings(start.sura)
- firstSurahMap = populateArrayFromCursor(firstSurahCursor)
- lastSurahCursor = db.getAyahTimings(end.sura)
- lastSurahMap = populateArrayFromCursor(lastSurahCursor)
- } catch (sqlException: SQLException) {
- Timber.e(sqlException)
- } finally {
- DatabaseUtils.closeCursor(firstSurahCursor)
- DatabaseUtils.closeCursor(lastSurahCursor)
+ private fun getTimingData(
+ start: SuraAyah,
+ end: SuraAyah,
+ gaplessDatabase: String
+ ): Pair {
+ val db: SuraTimingDatabaseHandler =
+ SuraTimingDatabaseHandler.getDatabaseHandler(gaplessDatabase)
+ val firstSurahMap = db.getAyahTimings(start.sura)
+ val lastSurahMap = if (start.sura == end.sura) {
+ firstSurahMap
+ } else {
+ db.getAyahTimings(end.sura)
}
- return ArrayList(
- listOf(firstSurahMap, lastSurahMap))
- }
-
- private fun populateArrayFromCursor(cursor: Cursor?): SparseIntArray {
- val sparseIntArray = SparseIntArray()
- if (cursor != null && cursor.moveToFirst()) {
- do {
- val ayah = cursor.getInt(1)
- val time = cursor.getInt(2)
- sparseIntArray.put(ayah, time)
- } while (cursor.moveToNext())
- }
- return sparseIntArray
+ return firstSurahMap to lastSurahMap
}
private fun getSurahSegment(
@@ -245,17 +225,16 @@ class AudioShareUtils {
}
val tempAudioName = UUID.randomUUID().toString() + ".mp3"
val destFile = File(audioCacheDirectory.path + File.separator + tempAudioName)
- val soundFile = arrayOfNulls(1)
+ val soundFile = CheapSoundFile.create(path, null)
try {
- soundFile[0] = CheapSoundFile.create(path, null)
val startTime = lowerCut.toFloat() / 1000
val endTime = upperCut.toFloat() / 1000
- val samplesPerFrame = soundFile[0]?.samplesPerFrame
- val sampleRate = soundFile[0]?.sampleRate
- val avg = sampleRate?.div(samplesPerFrame!!)
- val startFrames = (startTime * avg!!).roundToInt()
+ val samplesPerFrame = soundFile.samplesPerFrame
+ val sampleRate = soundFile.sampleRate
+ val avg = sampleRate.div(samplesPerFrame)
+ val startFrames = (startTime * avg).roundToInt()
val endFrames = (endTime * avg).roundToInt()
- soundFile[0]?.WriteFile(destFile, startFrames, endFrames - startFrames)
+ soundFile.WriteFile(destFile, startFrames, endFrames - startFrames)
} catch (e: IOException) {
e.printStackTrace()
}