From 823ffd87080f12791a72d54508bb2393f4444d3c Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Thu, 24 Aug 2023 00:25:05 +0200 Subject: [PATCH 01/18] reverted low api crash handle crashing --- app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index 4b4747ae..5f3162b4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -107,7 +107,7 @@ class AcraApplication : Application() { override fun onCreate() { super.onCreate() - NativeCrashHandler.initCrashHandler() + //NativeCrashHandler.initCrashHandler() ExceptionHandler(filesDir.resolve("last_error")) { val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName) startActivity(Intent.makeRestartActivityTask(intent!!.component)) From 9a1358e295cf9761d3e8c9bba04ef214dcfc96ca Mon Sep 17 00:00:00 2001 From: CranberrySoup <142951702+CranberrySoup@users.noreply.github.com> Date: Thu, 24 Aug 2023 14:16:33 +0000 Subject: [PATCH 02/18] Lower targetSdk to get all installed packages (#571) --- app/build.gradle.kts | 2 +- app/src/main/AndroidManifest.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 178b49c2..dfd2c173 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -55,7 +55,7 @@ android { defaultConfig { applicationId = "com.lagradost.cloudstream3" minSdk = 21 - targetSdk = 33 + targetSdk = 29 versionCode = 59 versionName = "4.1.7" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 563c82f8..0e716034 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ - + Date: Thu, 24 Aug 2023 16:39:50 +0200 Subject: [PATCH 03/18] fixed removal of predownloaded files --- .../java/com/lagradost/cloudstream3/utils/storage/SafeFile.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/storage/SafeFile.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/storage/SafeFile.kt index 9ba0ef88..85a74963 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/storage/SafeFile.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/storage/SafeFile.kt @@ -23,7 +23,7 @@ interface SafeFile { // because UniFile sucks balls on Media we have to do this val absPath = file.absolutePath.removePrefix(File.separator) for (value in MediaFileContentType.values()) { - val prefixes = listOf(value.toAbsolutePath(), value.toPath()) + val prefixes = listOf(value.toAbsolutePath(), value.toPath()).map { it.removePrefix(File.separator) } for (prefix in prefixes) { if (!absPath.startsWith(prefix)) continue return fromMedia( From c92ac3e8b3502f26ed812647134dc0977218831e Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Thu, 24 Aug 2023 18:13:42 +0200 Subject: [PATCH 04/18] fixed removal of predownloaded files 2 + permission --- app/src/main/AndroidManifest.xml | 2 +- .../java/com/lagradost/cloudstream3/utils/storage/MediaFile.kt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0e716034..15767d7b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ - + Download + if(relativePath == path) return this + val newPath = sanitizedAbsolutePath + path + if (folder) File.separator else "" From 9b4701fe91858c71fa32ecec98c3fb05451dfc6c Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Thu, 24 Aug 2023 18:14:54 +0200 Subject: [PATCH 05/18] dont remove keys while this is tested --- .../com/lagradost/cloudstream3/utils/VideoDownloadManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 442fa32f..948d7b8a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -1682,7 +1682,7 @@ object VideoDownloadManager { // only delete the key if the file is not found if (file == null || !file.existsOrThrow()) { - if (removeKeys) context.removeKey(KEY_DOWNLOAD_INFO, id.toString()) + //if (removeKeys) context.removeKey(KEY_DOWNLOAD_INFO, id.toString()) // TODO READD return null } From 1a4cbcaea048e9d057f9c70e37a7a46330a0203c Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Thu, 24 Aug 2023 21:17:42 +0200 Subject: [PATCH 06/18] small fix --- .../ui/result/ResultViewModel2.kt | 4 +-- .../cloudstream3/utils/storage/MediaFile.kt | 27 +++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index bdd27091..82d9a8fe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -591,7 +591,7 @@ class ResultViewModel2 : ViewModel() { link, "$fileName ${link.name}", folder, - if (link.url.contains(".srt")) ".srt" else "vtt", + if (link.url.contains(".srt")) "srt" else "vtt", false, null, createNotificationCallback = {} ) @@ -719,7 +719,7 @@ class ResultViewModel2 : ViewModel() { ) ) } - .map { ExtractorSubtitleLink(it.name, it.url, "") } + .map { ExtractorSubtitleLink(it.name, it.url, "") }.take(3) .forEach { link -> val fileName = VideoDownloadManager.getFileName(context, meta) downloadSubtitle(context, link, fileName, folder) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/storage/MediaFile.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/storage/MediaFile.kt index 526d31ca..51b8adfe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/storage/MediaFile.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/storage/MediaFile.kt @@ -13,6 +13,7 @@ import com.hippo.unifile.UniRandomAccessFile import com.lagradost.cloudstream3.mvvm.logError import okhttp3.internal.closeQuietly import java.io.File +import java.io.FileNotFoundException import java.io.InputStream import java.io.OutputStream @@ -65,6 +66,10 @@ class MediaFile( private val external: Boolean = true, absolutePath: String, ) : SafeFile { + override fun toString(): String { + return sanitizedAbsolutePath + } + // this is the path relative to the download directory so "/hello/text.txt" = "hello/text.txt" is in fact "Download/hello/text.txt" private val sanitizedAbsolutePath: String = replaceDuplicateFileSeparators(absolutePath) @@ -130,7 +135,7 @@ class MediaFile( // VideoDownloadManager.sanitizeFilename(path.replace(File.separator, "")) // in case of duplicate path, aka Download -> Download - if(relativePath == path) return this + if (relativePath == path) return this val newPath = sanitizedAbsolutePath + path + if (folder) File.separator else "" @@ -246,12 +251,24 @@ class MediaFile( override fun length(): Long? { if (isDir) return null - val length = query()?.length ?: return null - if(length <= 0) { - val inputStream : InputStream = openInputStream() ?: return null + val query = query() + val length = query?.length ?: return null + if (length <= 0) { + try { + contentResolver.openFileDescriptor(query.uri, "r") + .use { + it?.statSize + }?.let { + return it + } + } catch (e: FileNotFoundException) { + return null + } + + val inputStream: InputStream = openInputStream() ?: return null return try { inputStream.available().toLong() - } catch (t : Throwable) { + } catch (t: Throwable) { null } finally { inputStream.closeQuietly() From b38a9b1ff5d6e1c5de21851af089232fca7c985b Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Thu, 24 Aug 2023 21:39:05 +0200 Subject: [PATCH 07/18] fuck android --- .../com/lagradost/cloudstream3/utils/VideoDownloadManager.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 948d7b8a..7bd863ae 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -62,6 +62,7 @@ const val DOWNLOAD_CHANNEL_DESCRIPT = "The download notification channel" object VideoDownloadManager { var maxConcurrentDownloads = 3 + var maxConcurrentConnections = 3 private var currentDownloads = mutableListOf() private const val USER_AGENT = @@ -1568,7 +1569,7 @@ object VideoDownloadManager { folder ?: "", ep.id, startIndex, - callback + callback, parallelConnections = maxConcurrentConnections ) } else { return downloadThing( @@ -1579,7 +1580,7 @@ object VideoDownloadManager { "mp4", tryResume, ep.id, - callback + callback, parallelConnections = maxConcurrentConnections ) } } catch (t: Throwable) { From 2d82480398c2dd5b0dfa5b0c0db7c626658aafd2 Mon Sep 17 00:00:00 2001 From: Sofie <117321707+Sofie99@users.noreply.github.com> Date: Fri, 25 Aug 2023 15:58:58 +0700 Subject: [PATCH 08/18] fix Rabbitstream (#573) Co-authored-by: Sofie99 --- .../com/lagradost/cloudstream3/extractors/Rabbitstream.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt index b686f7d8..0154b4e8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Rabbitstream.kt @@ -36,7 +36,6 @@ open class Rabbitstream : ExtractorApi() { override val requiresReferer = false open val embed = "ajax/embed-4" open val key = "https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt" - private var rawKey: String? = null override suspend fun getUrl( url: String, @@ -82,9 +81,10 @@ open class Rabbitstream : ExtractorApi() { ) } + } - private suspend fun getRawKey(): String = rawKey ?: app.get(key).text.also { rawKey = it } + private suspend fun getRawKey(): String = app.get(key).text private fun extractRealKey(originalString: String?, stops: String): Pair { val table = parseJson>>(stops) From d0c03321b90b13142dce2ab5931c13db48e4a90d Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Fri, 25 Aug 2023 10:59:18 +0200 Subject: [PATCH 09/18] Translations update from Hosted Weblate (#568) Co-authored-by: Carlos Luiz Co-authored-by: Joel Brink Co-authored-by: Julian Co-authored-by: Mubarek Seyd Juhar Co-authored-by: Sam Cooper Co-authored-by: Skrripy Co-authored-by: mbottari Co-authored-by: tabtomi8 --- app/src/main/res/values-ajp/strings.xml | 2 + app/src/main/res/values-am/strings.xml | 5 + app/src/main/res/values-ars/strings.xml | 203 +++++++++++++++++- app/src/main/res/values-bp/strings.xml | 69 +++++- app/src/main/res/values-de/strings.xml | 12 +- app/src/main/res/values-hu/strings.xml | 38 ++-- app/src/main/res/values-ti/strings.xml | 6 + app/src/main/res/values-uk/strings.xml | 4 +- .../metadata/android/ar-SA/changelogs/2.txt | 1 + .../android/ar-SA/full_description.txt | 10 +- .../android/ar-SA/short_description.txt | 2 +- fastlane/metadata/android/ar-SA/title.txt | 2 +- .../android/de-DE/full_description.txt | 6 +- 13 files changed, 321 insertions(+), 39 deletions(-) create mode 100644 app/src/main/res/values-ajp/strings.xml create mode 100644 app/src/main/res/values-am/strings.xml create mode 100644 app/src/main/res/values-ti/strings.xml create mode 100644 fastlane/metadata/android/ar-SA/changelogs/2.txt diff --git a/app/src/main/res/values-ajp/strings.xml b/app/src/main/res/values-ajp/strings.xml new file mode 100644 index 00000000..a6b3daec --- /dev/null +++ b/app/src/main/res/values-ajp/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values-am/strings.xml b/app/src/main/res/values-am/strings.xml new file mode 100644 index 00000000..98eb0e0d --- /dev/null +++ b/app/src/main/res/values-am/strings.xml @@ -0,0 +1,5 @@ + + + %s ክፍል %d + ተዋናዮች: %s + \ No newline at end of file diff --git a/app/src/main/res/values-ars/strings.xml b/app/src/main/res/values-ars/strings.xml index 42eba3cc..12d558ad 100644 --- a/app/src/main/res/values-ars/strings.xml +++ b/app/src/main/res/values-ars/strings.xml @@ -1,2 +1,203 @@ - + + لافتة + تغيير مزود + جارى التحميل + بث%s + ملء + تخطي التحميل + تحميل… + ترجمات + إعادة محاولة الاتصال … + %sييبي%d + الحلقة%dسيتم نشرها في + %dي%dس%dد + %dس%dد + %dد + لافتة الحلقة + اللافتة الاساسية + اذهب للخالف + معاينة الخلفية + سرعة(%.2fx) + فتح مع كلاودستريم + الصفحة الاساسية + ...%sابحث + لايوجد بيانات + المزيد من الخيارات + فتح في المتصفح + المتصفح + شاهد الفلم + دفق التورنت + بدأ التنزيل + عشوائي قادم + تشغيل المقطع الدعائي + الأنواع + توقف التنزيل + خطط للمشاهدة + لا يوجد + إعادة المشاهدة + !تم العثور على تحديث جديد +\n%s->%s + %.1f:قدر + %dاقل + كلاودستريم + بحث + التحميلات + اعدادات + ...بحث + الحلقة القادمة + شارك + مشاهدة + في التوقف + مكتمل + توقف + تشغيل البث المباشر + مصادر + تشغيل الحلقة + تم إلغاء التنزيل + تم التنزيل + تنززل + تحميل + عُد + التحميل فشل + استخدم سطوع النظام في مشغل التطبيق بدلاً من التراكب الداكن + تم تحميل ملف النسخ الاحتياطي + البحث المتقدم + إزالة الحدود السوداء + ترجمات + يضيف خيار السرعة في المشغل + انقر نقرا مزدوجا للبحث + انقر نقرًا مزدوجًا للإيقاف المؤقت + اللاعب يبحث عن المبلغ (بالثواني) + اسحب من جانب إلى آخر للتحكم بموقعك في الفيديو + ابدأ الحلقة التالية عندما تنتهي الحلقة الحالية + استخدام سطوع النظام + تحديث مراقبة التقدم + قم بمزامنة تقدم الحلقة الحالية تلقائيًا + اسحب لتغيير الإعدادات + استعادة البيانات من النسخة الاحتياطية + فشل في استعادة البيانات من الملف %s + انقر مرتين على الجانب الأيمن أو الأيسر للبحث للأمام أو للخلف + البيانات المخزنة + اضغط مرتين في المنتصف للتوقف مؤقتًا + أذونات التخزين مفقودة. حاول مرة اخرى. + حدث خطأ أثناء النسخ الاحتياطي %s + بحث + مكتبة + معلومات + التحديثات والنسخ الاحتياطي + يعطيك نتائج البحث مفصولة حسب المزود + يرسل فقط البيانات عن الأعطال + عرض المقطورات + عرض الملصقات من كيتسو + حسابات + لا يرسل أي بيانات + عرض حلقة حشو للأنمي + إخفاء جودة الفيديو المحددة في نتائج البحث + تحديثات البرنامج المساعد التلقائي + البحث تلقائيًا عن التحديثات الجديدة بعد بدء تشغيل التطبيق. + التحديث إلى الإصداراالمسبق + تنزيل المكونات الإضافية تلقائيًا + إعادة عملية الإعداد + ابحث عن تحديثات الإصدار التجريبي بدلاً من الإصدارات الكاملة فقط + حدد الوضع لتصفية تنزيل المكونات الإضافية + قم تلقائيًا بتثبيت جميع المكونات الإضافية التي لم يتم تثبيتها بعد من المستودعات المضافة. + إعدادات ترجمات كرومكاست + وضع إيجينجرافي + انتقد للبحث + نسخ إحتياطي للبيانات + إظهار تحديثات التطبيق + إعدادات ترجمات المشغل + ترجمات كرومكاست + قم بالتمرير لأعلى أو لأسفل على الجانب الأيسر أو الأيمن لتغيير السطوع أو مستوى الصوت + التشغيل التلقائي للحلقة القادمة + تطبيق رواية خفيفة من نفس المطورين + أعط بينيني للمطورين + جيتهب + تطبيق انيمي من نفس المطورين + لغة التطبيق + انضم إلى الديسكورد + بنيني معطا + بعض الهواتف لا تدعم مثبت الحزمة الجديد. جرب الخيار القديم إذا لم يتم تثبيت التحديثات. + مثبت تتبيق + اجتاز + الحلقات + موسم + تم نسخ الرابط إلى الحافظة + مسح + وقف + جارٍ تنزيل تحديث التطبيق… + إعادة التعيين إلى القيمة العادية + س + %d%s + لا يتمتع هذا المزود بدعم كرومكاست + لم يتم العثور على أي روابط + تشغيل الحلقة + عذرًا، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين + %s%d%s + لا يوجد موسم + حلقة + %d-%d + يي + امسح التاريخ + جارٍ تثبيت تحديث التطبيق… + بدأ + لم يتم العثور على أي حلقات + إظهار تخطي النوافذ المنبثقة للفتح/الإنهاء + الكثير من النص. غير قادر على الحفظ في الحافظة. + وضع علامة كما شاهدت + إزالة من شاهد + حذف ملف + فشل + اكتمل + -30 + +30 + تاريخ + هل أنت متأكد أنك تريد الخروج؟ + نعم + لا + تعذر تثبيت الإصدار الجديد من التطبيق + إرث + منزل المجموعة + التقييم (من الأقل إلى الأعلى) + تم التحديث (من الجديد إلى القديم) + تم التحديث (القديم إلى الجديد) + أبجديًا (من الألف إلى الياء) + مكتبتك فارغة :( +\nقم بتسجيل الدخول إلى حساب المكتبة أو قم بإضافة العروض إلى مكتبتك المحلية. + !تم العثور على ملف الوضع الآمن +\n.عدم تحميل أي ملحقات عند بدء التشغيل حتى تتم إزالة الملف + ارجع + تحديث العروض المشتركة + الوضع العادي + حرر + ملفات تعريفية + مساعدة + .هنا يمكنك تغيير كيفية ترتيب المصادر. إذا كان للفيديو أولوية أعلى، فسيظهر في مكان أعلى في تحديد المصدر. مجموع أولوية المصدر وأولوية الجودة هو أولوية الفيديو +\n +\nالمصدر أ: 3 +\nالجودة ب: 7 +\nستكون أولوية الفيديو المدمجة .10 +\n +\n!ملاحظة: إذا كان المجموع 10 أو أكثر، فسيقوم اللاعب تلقائيًا بتخطي التحميل عند تحميل هذا الرابط + لقد صوت بالفعل + أبجديًا (ياء إلى ألف) + ترتيب حسب + مشترك + سيتم تحديث التطبيق عند الخروج + رتب + التقييم (من الأعلى إلى الأقل) + حدد المكتبة + افتع مع + .هذه القائمة فارغة. حاول التبديل إلى واحد آخر + %sتم الاشتراك في + %sتم إلغاء الاشتراك من + !%dتم إصدار الحلقة + خلفية الملف الشخصي + %dملف التعريف + واي فاي + بيانات الجوال + استخدم + %sتعذر إنشاء واجهة المستخدم بشكل صحيح، وهذا خطأ كبير ويجب الإبلاغ عنه على الفور + الصفات + \ No newline at end of file diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-bp/strings.xml index 425293e4..df95d69f 100644 --- a/app/src/main/res/values-bp/strings.xml +++ b/app/src/main/res/values-bp/strings.xml @@ -156,7 +156,7 @@ Não enviar nenhum dado Mostrar episódios de Filler em anime Mostrar trailers - Mostrar posters do kitsu + Mostrar posters do Kitsu Esconder qualidades de vídeo selecionadas nos resultados da Pesquisa Atualizações de plugin automáticas Mostrar atualizações do app @@ -183,7 +183,7 @@ S E Nenhum Episódio encontrado - Deletar Arquivo + Apagar Arquivo Deletar Pausar Retomar @@ -410,15 +410,19 @@ Transferido %d %s com sucesso Tudo %s já transferido Transferência em batch - plugin - plugins + Plugin + Plugins Isto irá apagar todos os repositórios de plugins Apagar repositório Transferir lista de sites a usar Transferido: %d Desativado: %d Não transferido: %d - Adicionar um repositório para instalar extensões de sites + CloudStream não tem fontes instaladas por padrão. Você precisa instalar um site de repositórios. +\n +\nPor causa das limitações do DMCA (Digital Millennium Copyright Act ) feito em nome de Sky UK Limited 🤮nós não podemos adicionar site de repositórios no app. +\n +\nEntre no nosso Discord ou pesquise online. Ver repositórios da comunidade Lista pública Todas as legendas em maiúsculas @@ -455,7 +459,7 @@ Editar Perfis Exibindo Player - procure na Barra de Progresso - remover dos assitidos + Remover dos assistidos Extensões Alfabética(A => Z) Abrir com @@ -468,7 +472,7 @@ Biblioteca Não Trilhas Sonoras - Votação(Baixa para Alta) + Votação (Baixa para Alta) Atualização iniciada Conteúdo +18 Ajuda @@ -476,7 +480,7 @@ Não pudemos instalar a nova versão do App instalador de pacotes Organizar por - Votação(Alta para Baixa) + Votação (Alta para Baixa) Alfabética(Z => A) Qualidade Perfil de plano de fundo @@ -499,4 +503,51 @@ Atualizando shows inscritos Player oculto - Procure na barra de progresso Conteúdo +18 - + Reiniciar + Parar + Marcar como assistido + Aplicativo precisa ser fechado para atualizar + Mostrar popups pulados para abertura e finalização + %d-%d + Player interno + Tamanho + Abrindo + %s %d%s + %d plugins atualizados + Todos as extensões serão desligadas para ajuda se talvez estejam causando algum bug. + Aplicativo não encontrado + Recapitular + Todas as linguagens + Pula %s + Mistura terminada + Modo seguro ligado + Ranquear: %s + Linguagem + Lista de reprodução HLS + Terminando + %d %s + Adicionado em (antigo para novo) + Introdução + plug-ins não foram encontrados no repositório + Repositório não encontrado, verifique o URL e tente usa uma VPN + Descrição + Versão + Autores + Instale a extensão primeiro + Créditos + Historico + Limpar historico + Tem Muito texto. Não é possível salvar no clipboard. + Player de vídeo preferido + Começar + Suportado + Status + MPV + Abrindo mistura + VLC + Aplicar quando reiniciar + Visualização info de crash + Faixas de áudio + Adicionado em (novo para antigo) + Faixas de video + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 45a6a66c..6892c8fd 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -92,7 +92,7 @@ Abbrechen Kopieren Schließen - Löschen + Leeren Speichern Player-Geschwindigkeit Untertiteleinstellungen @@ -390,7 +390,7 @@ Einrichtung überspringen Aussehen der App passend zu dem des Geräts ändern Absturzmeldung - Was möchtest du anschauen\? + Was möchten Sie sehen\? Fertig Erweiterungen Repository hinzufügen @@ -546,4 +546,10 @@ \nWerden eine kombinierte Videopriorität von 10 haben. \n \nHINWEIS: Wenn die Summe 10 oder mehr beträgt, überspringt der Player automatisch das Laden, wenn der Link geladen wird! - + Filtermodus für Plugin-Downloads auswählen + Es wurde bereits abgestimmt + Keine Plugins im Repository gefunden + Repository nicht gefunden, überprüfe die URL und probiere eine VPN + Die Benutzeroberfläche konnte nicht korrekt erstellt werden. Dies ist ein schwerwiegender Fehler und sollte sofort gemeldet werden. %s + Deaktivieren + \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 46407f76..ac817db0 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -190,7 +190,7 @@ Adatok eltárolva Hiba a biztonsági mentés során %s Fiókok - Szolgáltatás szerinti keresés eredmények + Szolgáltató szerint elkülönítve adja meg a keresési eredményeket Nem küld adatokat Poszterek megjelenítése Kitsu-ról Kiválasztott videóminőségek elrejtése keresési eredményekbe @@ -198,7 +198,7 @@ Bővítmények automatikus letöltése Automatikusan telepíti az összes még nem telepített bővítményt a hozzáadott tárolókból. Alkalmazás frissítések megjelenítése - Automatikusan keressen új frissítéseket indításkor + Automatikusan keressen új frissítéseket indításkor. Frissítés az előzetes kiadásokhoz (prerelease) Csak előzetesen kiadott frissítések (prerelease) keresése a teljes kiadások helyett Github @@ -232,30 +232,30 @@ Lejátszás böngészőben Feliratok letöltése Újracsatlakozás… - Swipe balra vagy jobbra a videólejátszóban az idő vezérléséhez + Húzd balra vagy jobbra a videólejátszóban az idő vezérléséhez Csúsztassa ujját a beállítások módosításához - Csúsztassa az újját bal vagy jobb oldalon a fényerő vagy hangerő megváltoztatásához + Csúsztassa felfelé vagy lefelé a bal vagy jobb oldalon a fényerő vagy a hangerő megváltoztatásához Biztonsági mentés 0 Banán a fejlesztőknek - Swipe to seek + Húzás a kereséshez Következő epizód automatikus lejátszása Következő epizód lejátszása amikor az aktuális epizód véget ér - Dupla koppintás to seek + Dupla koppintás a kereséshez Dupla koppintás a szüneteltetéshez - Player seek amount + Lejátszó keresési értéke (Másodpercben) Koppintson kétszer a jobb vagy bal oldalra az előre vagy hátra ugráshoz - Koppintson középre a szüneteltetéshez + Koppintson kétszer középen a szüneteltetéshez Rendszer fényerejének használata Rendszer fényerejének használata az appban a sötét átfedés helyett Előrehaladás frissítése Automatikusan szinkronizálja az aktuális epizód előrehaladását - Adatok visszaállítása a biztonsági mentésből + Adatok visszaállítása biztonsági mentésből Biztonsági mentés betöltve Információ Folytatás -30 Frissítés elkezdődött - Nem sikerült visszaállítani az adatok a fájlból %s + Nem sikerült visszaállítani az adatokat a %s fájlból Tárolási engedélyek hiányoznak. Kérjük próbálja újra. Csak összeomlásokról küld adatokat APK Telepítő @@ -280,7 +280,7 @@ DNS HTTPS-en keresztül Böngésző Android TV - kézmozdulatok + Kézmozdulatok frissítés kihagyása Alkalmazásfrissítések Szolgáltatók @@ -496,4 +496,18 @@ HQ %d letöltve Start - + Emulátor elrendezés + Nyomkövetés hozzáadása + Telefon elrendezés + Poszter cím helye + Tegye a címet a poszter alá + Az átugrás mértéke, amikor a lejátszó el van rejtve + Jogi nyilatkozat + Lejátszó megjelenítve - Ugrási Érték + Lejátszó elrejtve - Ugrási Érték + Klónozott oldal + Egy meglévő webhely klónjának hozzáadása, más URL-címmel + TV elrendezés + Automatikus + Az átugrás mértéke, amikor a lejátszó látható + \ No newline at end of file diff --git a/app/src/main/res/values-ti/strings.xml b/app/src/main/res/values-ti/strings.xml new file mode 100644 index 00000000..0f64858d --- /dev/null +++ b/app/src/main/res/values-ti/strings.xml @@ -0,0 +1,6 @@ + + + %s ክፋል %d + ክፋል %d በ ላይ ይወጣል + ተዋሳእቲ፡ %s + \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index e0db1c0e..8b1b6c39 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -418,7 +418,7 @@ Почалося завантаження %d %s… Завантажено %d %s Всі %s вже завантажено - Пакетне завантаження + Завантажити пакети плагін плагіни Видалити репозиторій @@ -553,4 +553,4 @@ Репозиторій не знайдено, перевірте URL-адресу та спробуйте VPN Не знайдено жодних плагінів у репозиторії Ви вже проголосували - + \ No newline at end of file diff --git a/fastlane/metadata/android/ar-SA/changelogs/2.txt b/fastlane/metadata/android/ar-SA/changelogs/2.txt new file mode 100644 index 00000000..cc43acf1 --- /dev/null +++ b/fastlane/metadata/android/ar-SA/changelogs/2.txt @@ -0,0 +1 @@ +تمت إضافة سجل التغيير! diff --git a/fastlane/metadata/android/ar-SA/full_description.txt b/fastlane/metadata/android/ar-SA/full_description.txt index 2107b338..9668a9b1 100644 --- a/fastlane/metadata/android/ar-SA/full_description.txt +++ b/fastlane/metadata/android/ar-SA/full_description.txt @@ -1,14 +1,10 @@ -يتيح لك كلاود ستريم -3 بث وتنزيل الأفلام والمسلسلات التلفزيونية والأنيمي. يأتي التطبيق بدون أي إعلانات وتحليلات. و يدعم العديد من مواقع البث الاولي(التريلر) والأفلام والمزيد. وتشمل الميزات: - +يسمح لك كلاود ستريم -3 ببث وتنزيل الأفلام, المسلسلات التلفزيونية, والأنيمي. +يأتي التطبيق بدون أي إعلانات وتحليلات و + يدعم العديد من مواقع البث الاولي(التريلر) ,والأفلام, والمزيد. إشارات مرجعية - -قم بتنزيل ودفق الأفلام والبرامج التلفزيونية والأنيمي - - تنزيلات الترجمة - دعم كروم كاست diff --git a/fastlane/metadata/android/ar-SA/short_description.txt b/fastlane/metadata/android/ar-SA/short_description.txt index f396ff81..7ccd9743 100644 --- a/fastlane/metadata/android/ar-SA/short_description.txt +++ b/fastlane/metadata/android/ar-SA/short_description.txt @@ -1 +1 @@ -بث وتحميل الأفلام والأنمي والمسلسلات التلفزيونية. +بث وتحميل الأفلام, الأنمي, والمسلسلات التلفزيونية. diff --git a/fastlane/metadata/android/ar-SA/title.txt b/fastlane/metadata/android/ar-SA/title.txt index 635e1390..7977b290 100644 --- a/fastlane/metadata/android/ar-SA/title.txt +++ b/fastlane/metadata/android/ar-SA/title.txt @@ -1 +1 @@ -كلاود ستريم +كلاودستريم diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt index df314372..ea2a8750 100644 --- a/fastlane/metadata/android/de-DE/full_description.txt +++ b/fastlane/metadata/android/de-DE/full_description.txt @@ -1,11 +1,11 @@ -Mit CloudStream-3 kannst du Filme, TV-Serien und Anime streamen und herunterladen. +Mit CloudStream-3 kannst du Filme, TV-Serien und Anime streamen und herunterladen. Die App kommt ganz ohne Werbung und Analytik aus. -Sie unterstützt mehrere Trailer-, Filmseiten und vieles mehr. Integrierte Features: +Sie unterstützt zahlreiche Trailer, Filmseiten und vieles mehr, unter anderem: Lesezeichen -Herunterladen und Streamen von Filmen, Fernsehsendungen und Animes +Herunterladen und Streaming von Filmen, Fernsehsendungen und Animes Downloads von Untertiteln From 557003895b65a7b6e1631489523e1225d56cdc34 Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Fri, 25 Aug 2023 08:59:37 +0000 Subject: [PATCH 10/18] chore(locales): fix locale issues --- .../com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt | 3 +++ app/src/main/res/values-ajp/strings.xml | 2 +- app/src/main/res/values-am/strings.xml | 2 +- app/src/main/res/values-ars/strings.xml | 2 +- app/src/main/res/values-bp/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-hu/strings.xml | 2 +- app/src/main/res/values-ti/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- 9 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index f46aac9b..1bd9778e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -54,6 +54,8 @@ fun getCurrentLocale(context: Context): String { // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes leave blank for auto val appLanguages = arrayListOf( /* begin language list */ + Triple("", "ajp", "ajp"), + Triple("", "አማርኛ", "am"), Triple("", "العربية", "ar"), Triple("", "ars", "ars"), Triple("", "български", "bg"), @@ -96,6 +98,7 @@ val appLanguages = arrayListOf( Triple("", "Soomaaliga", "so"), Triple("", "svenska", "sv"), Triple("", "தமிழ்", "ta"), + Triple("", "ትግርኛ", "ti"), Triple("", "Tagalog", "tl"), Triple("", "Türkçe", "tr"), Triple("", "українська", "uk"), diff --git a/app/src/main/res/values-ajp/strings.xml b/app/src/main/res/values-ajp/strings.xml index a6b3daec..42eba3cc 100644 --- a/app/src/main/res/values-ajp/strings.xml +++ b/app/src/main/res/values-ajp/strings.xml @@ -1,2 +1,2 @@ - \ No newline at end of file + diff --git a/app/src/main/res/values-am/strings.xml b/app/src/main/res/values-am/strings.xml index 98eb0e0d..5a799eb4 100644 --- a/app/src/main/res/values-am/strings.xml +++ b/app/src/main/res/values-am/strings.xml @@ -2,4 +2,4 @@ %s ክፍል %d ተዋናዮች: %s - \ No newline at end of file + diff --git a/app/src/main/res/values-ars/strings.xml b/app/src/main/res/values-ars/strings.xml index 12d558ad..ea8aa05c 100644 --- a/app/src/main/res/values-ars/strings.xml +++ b/app/src/main/res/values-ars/strings.xml @@ -200,4 +200,4 @@ استخدم %sتعذر إنشاء واجهة المستخدم بشكل صحيح، وهذا خطأ كبير ويجب الإبلاغ عنه على الفور الصفات - \ No newline at end of file + diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-bp/strings.xml index df95d69f..b70eec12 100644 --- a/app/src/main/res/values-bp/strings.xml +++ b/app/src/main/res/values-bp/strings.xml @@ -550,4 +550,4 @@ Faixas de áudio Adicionado em (novo para antigo) Faixas de video - \ No newline at end of file + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6892c8fd..6739465a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -552,4 +552,4 @@ Repository nicht gefunden, überprüfe die URL und probiere eine VPN Die Benutzeroberfläche konnte nicht korrekt erstellt werden. Dies ist ein schwerwiegender Fehler und sollte sofort gemeldet werden. %s Deaktivieren - \ No newline at end of file + diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index ac817db0..05a7f0a7 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -510,4 +510,4 @@ TV elrendezés Automatikus Az átugrás mértéke, amikor a lejátszó látható - \ No newline at end of file + diff --git a/app/src/main/res/values-ti/strings.xml b/app/src/main/res/values-ti/strings.xml index 0f64858d..a9079ed5 100644 --- a/app/src/main/res/values-ti/strings.xml +++ b/app/src/main/res/values-ti/strings.xml @@ -3,4 +3,4 @@ %s ክፋል %d ክፋል %d በ ላይ ይወጣል ተዋሳእቲ፡ %s - \ No newline at end of file + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 8b1b6c39..4866ecd4 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -553,4 +553,4 @@ Репозиторій не знайдено, перевірте URL-адресу та спробуйте VPN Не знайдено жодних плагінів у репозиторії Ви вже проголосували - \ No newline at end of file + From 8193e39b3046192dfb6970e5ff49f30d629d033a Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Fri, 25 Aug 2023 23:16:34 +0200 Subject: [PATCH 11/18] bump + refactor --- app/build.gradle.kts | 15 +- .../lagradost/cloudstream3/MainActivity.kt | 18 +- .../cloudstream3/NativeCrashHandler.kt | 4 +- .../ui/player/DownloadedPlayerActivity.kt | 8 +- .../cloudstream3/ui/player/GeneratorPlayer.kt | 6 +- .../ui/settings/SettingsGeneral.kt | 4 +- .../cloudstream3/utils/BackupUtils.kt | 1 + .../utils/VideoDownloadManager.kt | 14 +- .../cloudstream3/utils/storage/MediaFile.kt | 389 ------------------ .../cloudstream3/utils/storage/SafeFile.kt | 244 ----------- .../utils/storage/UniFileWrapper.kt | 116 ------ 11 files changed, 40 insertions(+), 779 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/storage/MediaFile.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/storage/SafeFile.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/storage/UniFileWrapper.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dfd2c173..333fbfb8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,11 +32,12 @@ android { enable = true } - externalNativeBuild { - cmake { - path("CMakeLists.txt") - } - } + // disable this for now + //externalNativeBuild { + // cmake { + // path("CMakeLists.txt") + // } + //} signingConfigs { create("prerelease") { @@ -58,7 +59,7 @@ android { targetSdk = 29 versionCode = 59 - versionName = "4.1.7" + versionName = "4.1.8" resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "") @@ -232,7 +233,7 @@ dependencies { // To fix SSL fuckery on android 9 implementation("org.conscrypt:conscrypt-android:2.2.1") // Util to skip the URI file fuckery 🙏 - implementation("com.github.tachiyomiorg:unifile:17bec43") + implementation("com.github.LagradOst:SafeFile:0.0.2") // API because cba maintaining it myself implementation("com.uwetrottmann.tmdb2:tmdb-java:2.6.0") diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 15b16078..fbad4fce 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -144,6 +144,7 @@ import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.ResponseParser +import com.lagradost.safefile.SafeFile import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import java.io.File @@ -279,6 +280,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { companion object { const val TAG = "MAINACT" var lastError: String? = null + /** * Setting this will automatically enter the query in the search * next time the search fragment is opened. @@ -366,7 +368,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { nextSearchQuery = try { URLDecoder.decode(query, "UTF-8") - } catch (t : Throwable) { + } catch (t: Throwable) { logError(t) query } @@ -859,7 +861,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { RecyclerView::class.java.declaredMethods.firstOrNull { it.name == "scrollStep" }?.also { it.isAccessible = true } - } catch (t : Throwable) { + } catch (t: Throwable) { null } } @@ -906,11 +908,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { if (dx > 0) dx else 0 } - if(!NO_MOVE_LIST) { + if (!NO_MOVE_LIST) { parent.smoothScrollBy(rdx, 0) - }else { + } else { val smoothScroll = reflectedScroll - if(smoothScroll == null) { + if (smoothScroll == null) { parent.smoothScrollBy(rdx, 0) } else { try { @@ -920,12 +922,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { val out = IntArray(2) smoothScroll.invoke(parent, rdx, 0, out) val scrolledX = out[0] - if(abs(scrolledX) <= 0) { // newFocus.measuredWidth*2 + if (abs(scrolledX) <= 0) { // newFocus.measuredWidth*2 smoothScroll.invoke(parent, -rdx, 0, out) parent.smoothScrollBy(scrolledX, 0) if (NO_MOVE_LIST) targetDx = scrolledX } - } catch (t : Throwable) { + } catch (t: Throwable) { parent.smoothScrollBy(rdx, 0) } } @@ -1131,10 +1133,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { snackbar.show() } } - } } + ioSafe { SafeFile.check(this@MainActivity) } if (PluginManager.checkSafeModeFile()) { normalSafeApiCall { diff --git a/app/src/main/java/com/lagradost/cloudstream3/NativeCrashHandler.kt b/app/src/main/java/com/lagradost/cloudstream3/NativeCrashHandler.kt index 1fe00748..7be90440 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/NativeCrashHandler.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/NativeCrashHandler.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.launch object NativeCrashHandler { // external fun triggerNativeCrash() - private external fun initNativeCrashHandler() + /*private external fun initNativeCrashHandler() private external fun getSignalStatus(): Int private fun initSignalPolling() = CoroutineScope(Dispatchers.IO).launch { @@ -49,5 +49,5 @@ object NativeCrashHandler { } initSignalPolling() - } + }*/ } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt index 03405faf..d181e175 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.player +import android.content.ContentUris import android.content.Intent import android.net.Uri import android.os.Bundle @@ -10,7 +11,7 @@ import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.UIHelper.navigate -import com.lagradost.cloudstream3.utils.storage.SafeFile +import com.lagradost.safefile.SafeFile const val DTAG = "PlayerActivity" @@ -57,7 +58,10 @@ class DownloadedPlayerActivity : AppCompatActivity() { listOf( ExtractorUri( uri = uri, - name = name ?: getString(R.string.downloaded_file) + name = name ?: getString(R.string.downloaded_file), + // well not the same as a normal id, but we take it as users may want to + // play downloaded files and save the location + id = kotlin.runCatching { ContentUris.parseId(uri) }.getOrNull()?.hashCode() ) ) ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 341b4ad3..2b9304b6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -21,13 +21,11 @@ import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager import androidx.media3.common.Format.NO_VALUE import androidx.media3.common.MimeTypes -import com.hippo.unifile.UniFile import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.databinding.DialogOnlineSubtitlesBinding import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding -import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding import com.lagradost.cloudstream3.databinding.PlayerSelectSourceAndSubsBinding import com.lagradost.cloudstream3.databinding.PlayerSelectTracksBinding import com.lagradost.cloudstream3.mvvm.* @@ -52,7 +50,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.toPx -import com.lagradost.cloudstream3.utils.storage.SafeFile +import com.lagradost.safefile.SafeFile import kotlinx.coroutines.Job import java.util.* import kotlin.math.abs @@ -136,7 +134,7 @@ class GeneratorPlayer : FullScreenPlayer() { return durPos.position } - var currentVerifyLink: Job? = null + private var currentVerifyLink: Job? = null private fun loadExtractorJob(extractorLink: ExtractorLink?) { currentVerifyLink?.cancel() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index f46aac9b..49f678c4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -38,7 +38,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath -import com.lagradost.cloudstream3.utils.storage.SafeFile +import com.lagradost.safefile.SafeFile fun getCurrentLocale(context: Context): String { val res = context.resources @@ -335,7 +335,7 @@ class SettingsGeneral : PreferenceFragmentCompat() { val currentDir = settingsManager.getString(getString(R.string.download_path_pref), null) - ?: context?.let { ctx -> VideoDownloadManager.getDefaultDir(ctx).toString() } + ?: context?.let { ctx -> VideoDownloadManager.getDefaultDir(ctx)?.filePath() } activity?.showBottomDialog( dirs + listOf("Custom"), diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt index 2da54678..41326eb8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt @@ -158,6 +158,7 @@ object BackupUtils { val displayName = "CS3_Backup_${date}" val backupFile = getBackup() val stream = setupStream(this, displayName, null, ext, false) + fileStream = stream.openNew() printStream = PrintWriter(fileStream) printStream.print(mapper.writeValueAsString(backupFile)) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 7bd863ae..6425ba66 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -35,8 +35,8 @@ import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute -import com.lagradost.cloudstream3.utils.storage.MediaFileContentType -import com.lagradost.cloudstream3.utils.storage.SafeFile +import com.lagradost.safefile.MediaFileContentType +import com.lagradost.safefile.SafeFile import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -554,9 +554,8 @@ object VideoDownloadManager { extension: String, tryResume: Boolean, ): StreamData { - val (base, _) = context.getBasePath() return setupStream( - base ?: throw IOException("Bad config"), + context.getBasePath().first ?: getDefaultDir(context) ?: throw IOException("Bad config"), name, folder, extension, @@ -1401,7 +1400,12 @@ object VideoDownloadManager { metadata.type = DownloadType.IsFailed } } finally { - fileMutex.unlock() + try { + // may cause java.lang.IllegalStateException: Mutex is not locked because of cancelling + fileMutex.unlock() + } catch (t : Throwable) { + logError(t) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/storage/MediaFile.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/storage/MediaFile.kt deleted file mode 100644 index 51b8adfe..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/storage/MediaFile.kt +++ /dev/null @@ -1,389 +0,0 @@ -package com.lagradost.cloudstream3.utils.storage - -import android.content.ContentResolver -import android.content.ContentUris -import android.content.ContentValues -import android.content.Context -import android.net.Uri -import android.os.Build -import android.os.Environment -import android.provider.MediaStore -import androidx.annotation.RequiresApi -import com.hippo.unifile.UniRandomAccessFile -import com.lagradost.cloudstream3.mvvm.logError -import okhttp3.internal.closeQuietly -import java.io.File -import java.io.FileNotFoundException -import java.io.InputStream -import java.io.OutputStream - - -enum class MediaFileContentType { - Downloads, - Audio, - Video, - Images, -} - -// https://developer.android.com/training/data-storage/shared/media -fun MediaFileContentType.toPath(): String { - return when (this) { - MediaFileContentType.Downloads -> Environment.DIRECTORY_DOWNLOADS - MediaFileContentType.Audio -> Environment.DIRECTORY_MUSIC - MediaFileContentType.Video -> Environment.DIRECTORY_MOVIES - MediaFileContentType.Images -> Environment.DIRECTORY_DCIM - } -} - -fun MediaFileContentType.defaultPrefix(): String { - return Environment.getExternalStorageDirectory().absolutePath -} - -fun MediaFileContentType.toAbsolutePath(): String { - return defaultPrefix() + File.separator + - this.toPath() -} - -fun replaceDuplicateFileSeparators(path: String): String { - return path.replace(Regex("${File.separator}+"), File.separator) -} - -@RequiresApi(Build.VERSION_CODES.Q) -fun MediaFileContentType.toUri(external: Boolean): Uri { - val volume = if (external) MediaStore.VOLUME_EXTERNAL_PRIMARY else MediaStore.VOLUME_INTERNAL - return when (this) { - MediaFileContentType.Downloads -> MediaStore.Downloads.getContentUri(volume) - MediaFileContentType.Audio -> MediaStore.Audio.Media.getContentUri(volume) - MediaFileContentType.Video -> MediaStore.Video.Media.getContentUri(volume) - MediaFileContentType.Images -> MediaStore.Images.Media.getContentUri(volume) - } -} - -@RequiresApi(Build.VERSION_CODES.Q) -class MediaFile( - private val context: Context, - private val folderType: MediaFileContentType, - private val external: Boolean = true, - absolutePath: String, -) : SafeFile { - override fun toString(): String { - return sanitizedAbsolutePath - } - - // this is the path relative to the download directory so "/hello/text.txt" = "hello/text.txt" is in fact "Download/hello/text.txt" - private val sanitizedAbsolutePath: String = - replaceDuplicateFileSeparators(absolutePath) - - // this is only a directory if the filepath ends with a / - private val isDir: Boolean = sanitizedAbsolutePath.endsWith(File.separator) - private val isFile: Boolean = !isDir - - // this is the relative path including the Download directory, so "/hello/text.txt" => "Download/hello" - private val relativePath: String = - replaceDuplicateFileSeparators(folderType.toPath() + File.separator + sanitizedAbsolutePath).substringBeforeLast( - File.separator - ) - - // "/hello/text.txt" => "text.txt" - private val namePath: String = sanitizedAbsolutePath.substringAfterLast(File.separator) - private val baseUri = folderType.toUri(external) - private val contentResolver: ContentResolver = context.contentResolver - - init { - // some standard asserts that should always be hold or else this class wont work - assert(!relativePath.endsWith(File.separator)) - assert(!(isDir && isFile)) - assert(!relativePath.contains(File.separator + File.separator)) - assert(!namePath.contains(File.separator)) - - if (isDir) { - assert(namePath.isBlank()) - } else { - assert(namePath.isNotBlank()) - } - } - - companion object { - private fun splitFilenameExt(name: String): Pair { - val split = name.indexOfLast { it == '.' } - if (split <= 0) return name to null - val ext = name.substring(split + 1 until name.length) - if (ext.isBlank()) return name to null - - return name.substring(0 until split) to ext - } - - private fun splitFilenameMime(name: String): Pair { - val (display, ext) = splitFilenameExt(name) - val mimeType = when (ext) { - - // Absolutely ridiculous, if text/vtt is used as mimetype scoped storage prevents - // downloading to /Downloads yet it works with null - - "vtt" -> null // "text/vtt" - "mp4" -> "video/mp4" - "srt" -> null // "application/x-subrip"//"text/plain" - else -> null - } - return display to mimeType - } - } - - private fun appendRelativePath(path: String, folder: Boolean): MediaFile? { - if (isFile) return null - - // VideoDownloadManager.sanitizeFilename(path.replace(File.separator, "")) - - // in case of duplicate path, aka Download -> Download - if (relativePath == path) return this - - val newPath = - sanitizedAbsolutePath + path + if (folder) File.separator else "" - - return MediaFile( - context = context, - folderType = folderType, - external = external, - absolutePath = newPath - ) - } - - private fun createUri(displayName: String? = namePath): Uri? { - if (displayName == null) return null - if (isFile) return null - val (name, mime) = splitFilenameMime(displayName) - - val newFile = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) - put(MediaStore.MediaColumns.TITLE, name) - if (mime != null) - put(MediaStore.MediaColumns.MIME_TYPE, mime) - put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath) - } - return contentResolver.insert(baseUri, newFile) - } - - override fun createFile(displayName: String?): SafeFile? { - if (isFile || displayName == null) return null - query(displayName)?.uri ?: createUri(displayName) ?: return null - return appendRelativePath(displayName, false) //SafeFile.fromUri(context, ?: return null) - } - - override fun createDirectory(directoryName: String?): SafeFile? { - if (directoryName == null) return null - // we don't create a dir here tbh, just fake create it - return appendRelativePath(directoryName, true) - } - - private data class QueryResult( - val uri: Uri, - val lastModified: Long, - val length: Long, - ) - - @RequiresApi(Build.VERSION_CODES.Q) - private fun query(displayName: String = namePath): QueryResult? { - try { - //val (name, mime) = splitFilenameMime(fullName) - - val projection = arrayOf( - MediaStore.MediaColumns._ID, - MediaStore.MediaColumns.DATE_MODIFIED, - MediaStore.MediaColumns.SIZE, - ) - - val selection = - "${MediaStore.MediaColumns.RELATIVE_PATH}='$relativePath${File.separator}' AND ${MediaStore.MediaColumns.DISPLAY_NAME}='$displayName'" - - contentResolver.query( - baseUri, - projection, selection, null, null - )?.use { cursor -> - while (cursor.moveToNext()) { - val id = - cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)) - - return QueryResult( - uri = ContentUris.withAppendedId( - baseUri, id - ), - lastModified = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED)), - length = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)), - ) - } - } - } catch (t: Throwable) { - logError(t) - } - - return null - } - - override fun uri(): Uri? { - return query()?.uri - } - - override fun name(): String? { - if (isDir) return null - return namePath - } - - override fun type(): String? { - TODO("Not yet implemented") - } - - override fun filePath(): String { - return replaceDuplicateFileSeparators(relativePath + File.separator + namePath) - } - - override fun isDirectory(): Boolean { - return isDir - } - - override fun isFile(): Boolean { - return isFile - } - - override fun lastModified(): Long? { - if (isDir) return null - return query()?.lastModified - } - - override fun length(): Long? { - if (isDir) return null - val query = query() - val length = query?.length ?: return null - if (length <= 0) { - try { - contentResolver.openFileDescriptor(query.uri, "r") - .use { - it?.statSize - }?.let { - return it - } - } catch (e: FileNotFoundException) { - return null - } - - val inputStream: InputStream = openInputStream() ?: return null - return try { - inputStream.available().toLong() - } catch (t: Throwable) { - null - } finally { - inputStream.closeQuietly() - } - } - return length - } - - override fun canRead(): Boolean { - TODO("Not yet implemented") - } - - override fun canWrite(): Boolean { - TODO("Not yet implemented") - } - - private fun delete(uri: Uri): Boolean { - return contentResolver.delete(uri, null, null) > 0 - } - - override fun delete(): Boolean { - return if (isDir) { - (listFiles() ?: return false).all { - it.delete() - } - } else { - delete(uri() ?: return false) - } - } - - override fun exists(): Boolean { - if (isDir) return true - return query() != null - } - - override fun listFiles(): List? { - if (isFile) return null - try { - val projection = arrayOf( - MediaStore.MediaColumns.DISPLAY_NAME - ) - - val selection = - "${MediaStore.MediaColumns.RELATIVE_PATH}='$relativePath${File.separator}'" - contentResolver.query( - baseUri, - projection, selection, null, null - )?.use { cursor -> - val out = ArrayList(cursor.count) - while (cursor.moveToNext()) { - val nameIdx = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) - if (nameIdx == -1) continue - val name = cursor.getString(nameIdx) - - appendRelativePath(name, false)?.let { new -> - out.add(new) - } - } - - out - } - } catch (t: Throwable) { - logError(t) - } - return null - } - - override fun findFile(displayName: String?, ignoreCase: Boolean): SafeFile? { - if (isFile || displayName == null) return null - - val new = appendRelativePath(displayName, false) ?: return null - if (new.exists()) { - return new - } - - return null//SafeFile.fromUri(context, query(displayName ?: return null)?.uri ?: return null) - } - - override fun renameTo(name: String?): Boolean { - TODO("Not yet implemented") - } - - override fun openOutputStream(append: Boolean): OutputStream? { - try { - // use current file - uri()?.let { - return contentResolver.openOutputStream( - it, - if (append) "wa" else "wt" - ) - } - - // create a new file if current is not found, - // as we know it is new only write access is needed - createUri()?.let { - return contentResolver.openOutputStream( - it, - "w" - ) - } - return null - } catch (t: Throwable) { - return null - } - } - - override fun openInputStream(): InputStream? { - try { - return contentResolver.openInputStream(uri() ?: return null) - } catch (t: Throwable) { - return null - } - } - - override fun createRandomAccessFile(mode: String?): UniRandomAccessFile? { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/storage/SafeFile.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/storage/SafeFile.kt deleted file mode 100644 index 85a74963..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/storage/SafeFile.kt +++ /dev/null @@ -1,244 +0,0 @@ -package com.lagradost.cloudstream3.utils.storage - -import android.content.Context -import android.net.Uri -import android.os.Build -import android.os.Environment -import com.hippo.unifile.UniFile -import com.hippo.unifile.UniRandomAccessFile - -import java.io.File -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream - -interface SafeFile { - companion object { - fun fromUri(context: Context, uri: Uri): SafeFile? { - return UniFileWrapper(UniFile.fromUri(context, uri) ?: return null) - } - - fun fromFile(context: Context, file: File?): SafeFile? { - if (file == null) return null - // because UniFile sucks balls on Media we have to do this - val absPath = file.absolutePath.removePrefix(File.separator) - for (value in MediaFileContentType.values()) { - val prefixes = listOf(value.toAbsolutePath(), value.toPath()).map { it.removePrefix(File.separator) } - for (prefix in prefixes) { - if (!absPath.startsWith(prefix)) continue - return fromMedia( - context, - value, - absPath.removePrefix(prefix).ifBlank { File.separator } - ) - } - } - - return UniFileWrapper(UniFile.fromFile(file) ?: return null) - } - - fun fromAsset( - context: Context, - filename: String? - ): SafeFile? { - return UniFileWrapper( - UniFile.fromAsset(context.assets, filename ?: return null) ?: return null - ) - } - - fun fromResource( - context: Context, - id: Int - ): SafeFile? { - return UniFileWrapper( - UniFile.fromResource(context, id) ?: return null - ) - } - - fun fromMedia( - context: Context, - folderType: MediaFileContentType, - path: String = File.separator, - external: Boolean = true, - ): SafeFile? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - //fromUri(context, folderType.toUri(external))?.findFile(folderType.toPath())?.gotoDirectory(path) - - return MediaFile( - context = context, - folderType = folderType, - external = external, - absolutePath = path - ) - } else { - fromFile( - context, - File( - (Environment.getExternalStorageDirectory().absolutePath + File.separator + - folderType.toPath() + File.separator + folderType).replace( - File.separator + File.separator, - File.separator - ) - ) - ) - } - - } - } - - /*val uri: Uri? get() = getUri() - val name: String? get() = getName() - val type: String? get() = getType() - val filePath: String? get() = getFilePath() - val isFile: Boolean? get() = isFile() - val isDirectory: Boolean? get() = isDirectory() - val length: Long? get() = length() - val canRead: Boolean get() = canRead() - val canWrite: Boolean get() = canWrite() - val lastModified: Long? get() = lastModified()*/ - - @Throws(IOException::class) - fun isFileOrThrow(): Boolean { - return isFile() ?: throw IOException("Unable to get if file is a file or directory") - } - - @Throws(IOException::class) - fun lengthOrThrow(): Long { - return length() ?: throw IOException("Unable to get file length") - } - - @Throws(IOException::class) - fun isDirectoryOrThrow(): Boolean { - return isDirectory() ?: throw IOException("Unable to get if file is a directory") - } - - @Throws(IOException::class) - fun filePathOrThrow(): String { - return filePath() ?: throw IOException("Unable to get file path") - } - - @Throws(IOException::class) - fun uriOrThrow(): Uri { - return uri() ?: throw IOException("Unable to get uri") - } - - @Throws(IOException::class) - fun renameOrThrow(name: String?) { - if (!renameTo(name)) { - throw IOException("Unable to rename to $name") - } - } - - @Throws(IOException::class) - fun openOutputStreamOrThrow(append: Boolean = false): OutputStream { - return openOutputStream(append) ?: throw IOException("Unable to open output stream") - } - - @Throws(IOException::class) - fun openInputStreamOrThrow(): InputStream { - return openInputStream() ?: throw IOException("Unable to open input stream") - } - - @Throws(IOException::class) - fun existsOrThrow(): Boolean { - return exists() ?: throw IOException("Unable get if file exists") - } - - @Throws(IOException::class) - fun findFileOrThrow(displayName: String?, ignoreCase: Boolean = false): SafeFile { - return findFile(displayName, ignoreCase) ?: throw IOException("Unable find file") - } - - @Throws(IOException::class) - fun gotoDirectoryOrThrow( - directoryName: String?, - createMissingDirectories: Boolean = true - ): SafeFile { - return gotoDirectory(directoryName, createMissingDirectories) - ?: throw IOException("Unable to go to directory $directoryName") - } - - @Throws(IOException::class) - fun listFilesOrThrow(): List { - return listFiles() ?: throw IOException("Unable to get files") - } - - - @Throws(IOException::class) - fun createFileOrThrow(displayName: String?): SafeFile { - return createFile(displayName) ?: throw IOException("Unable to create file $displayName") - } - - @Throws(IOException::class) - fun createDirectoryOrThrow(directoryName: String?): SafeFile { - return createDirectory( - directoryName ?: throw IOException("Unable to create file with invalid name") - ) - ?: throw IOException("Unable to create directory $directoryName") - } - - @Throws(IOException::class) - fun deleteOrThrow() { - if (!delete()) { - throw IOException("Unable to delete file") - } - } - - /** file.gotoDirectory("a/b/c") -> "file/a/b/c/" where a null or blank directoryName - * returns itself. createMissingDirectories specifies if the dirs should be created - * when travelling or break at a dir not found */ - fun gotoDirectory( - directoryName: String?, - createMissingDirectories: Boolean = true - ): SafeFile? { - if (directoryName == null) return this - - return directoryName.split(File.separatorChar).filter { it.isNotBlank() } - .fold(this) { file: SafeFile?, directory -> - // as MediaFile does not actually create a directory we can do this - if (createMissingDirectories || this is MediaFile) { - file?.createDirectory(directory) - } else { - val next = file?.findFile(directory) - - // we require the file to be a directory - if (next?.isDirectory() != true) { - null - } else { - next - } - } - } - } - - - fun createFile(displayName: String?): SafeFile? - fun createDirectory(directoryName: String?): SafeFile? - fun uri(): Uri? - fun name(): String? - fun type(): String? - fun filePath(): String? - fun isDirectory(): Boolean? - fun isFile(): Boolean? - fun lastModified(): Long? - fun length(): Long? - fun canRead(): Boolean - fun canWrite(): Boolean - fun delete(): Boolean - fun exists(): Boolean? - fun listFiles(): List? - - // fun listFiles(filter: FilenameFilter?): Array? - fun findFile(displayName: String?, ignoreCase: Boolean = false): SafeFile? - - fun renameTo(name: String?): Boolean - - /** Open a stream on to the content associated with the file */ - fun openOutputStream(append: Boolean = false): OutputStream? - - /** Open a stream on to the content associated with the file */ - fun openInputStream(): InputStream? - - /** Get a random access stuff of the UniFile, "r" or "rw" */ - fun createRandomAccessFile(mode: String?): UniRandomAccessFile? -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/storage/UniFileWrapper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/storage/UniFileWrapper.kt deleted file mode 100644 index f1592169..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/storage/UniFileWrapper.kt +++ /dev/null @@ -1,116 +0,0 @@ -package com.lagradost.cloudstream3.utils.storage - -import android.net.Uri -import com.hippo.unifile.UniFile -import com.hippo.unifile.UniRandomAccessFile -import com.lagradost.cloudstream3.mvvm.logError -import okhttp3.internal.closeQuietly -import java.io.InputStream -import java.io.OutputStream - -private fun UniFile.toFile(): SafeFile { - return UniFileWrapper(this) -} - -fun safe(apiCall: () -> T): T? { - return try { - apiCall.invoke() - } catch (throwable: Throwable) { - logError(throwable) - null - } -} - -class UniFileWrapper(val file: UniFile) : SafeFile { - override fun createFile(displayName: String?): SafeFile? { - return file.createFile(displayName)?.toFile() - } - - override fun createDirectory(directoryName: String?): SafeFile? { - return file.createDirectory(directoryName)?.toFile() - } - - override fun uri(): Uri? { - return safe { file.uri } - } - - override fun name(): String? { - return safe { file.name } - } - - override fun type(): String? { - return safe { file.type } - } - - override fun filePath(): String? { - return safe { file.filePath } - } - - override fun isDirectory(): Boolean? { - return safe { file.isDirectory } - } - - override fun isFile(): Boolean? { - return safe { file.isFile } - } - - override fun lastModified(): Long? { - return safe { file.lastModified() } - } - - override fun length(): Long? { - return safe { - val len = file.length() - if (len <= 1) { - val inputStream = this.openInputStream() ?: return@safe null - try { - inputStream.available().toLong() - } finally { - inputStream.closeQuietly() - } - } else { - len - } - } - } - - override fun canRead(): Boolean { - return safe { file.canRead() } ?: false - } - - override fun canWrite(): Boolean { - return safe { file.canWrite() } ?: false - } - - override fun delete(): Boolean { - return safe { file.delete() } ?: false - } - - override fun exists(): Boolean? { - return safe { file.exists() } - } - - override fun listFiles(): List? { - return safe { file.listFiles()?.mapNotNull { it?.toFile() } } - } - - override fun findFile(displayName: String?, ignoreCase: Boolean): SafeFile? { - return safe { file.findFile(displayName, ignoreCase)?.toFile() } - } - - override fun renameTo(name: String?): Boolean { - return safe { file.renameTo(name) } ?: return false - } - - override fun openOutputStream(append: Boolean): OutputStream? { - return safe { file.openOutputStream(append) } - } - - override fun openInputStream(): InputStream? { - return safe { file.openInputStream() } - } - - override fun createRandomAccessFile(mode: String?): UniRandomAccessFile? { - return safe { file.createRandomAccessFile(mode) } - } -} \ No newline at end of file From f01820059b520912d77be56ce8a39c2530f6f886 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Sun, 27 Aug 2023 19:07:08 +0200 Subject: [PATCH 12/18] delete resume watching + delete bookmarks buttons. fixed backup crash --- .../cloudstream3/ui/home/HomeFragment.kt | 6 ++- .../ui/home/HomeParentItemAdapterPreview.kt | 31 ++++++++--- .../cloudstream3/ui/home/HomeViewModel.kt | 36 ++++++++++--- .../cloudstream3/utils/BackupUtils.kt | 53 +++---------------- .../cloudstream3/utils/DataStoreHelper.kt | 12 +++-- 5 files changed, 72 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index fa0b6dfb..b84c619e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -658,12 +658,14 @@ class HomeFragment : Fragment() { return@observeNullable } - bottomSheetDialog = activity?.loadHomepageList(item, expandCallback = { + val (items, delete) = item + + bottomSheetDialog = activity?.loadHomepageList(items, expandCallback = { homeViewModel.expandAndReturn(it) }, dismissCallback = { homeViewModel.popup(null) bottomSheetDialog = null - }) + }, deleteCallback = delete) } homeViewModel.reloadStored() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index 13497a99..1d8e1399 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -246,7 +246,7 @@ class HomeParentItemAdapterPreview( private val previewViewpagerText: ViewGroup = itemView.findViewById(R.id.home_preview_viewpager_text) - // private val previewHeader: FrameLayout = itemView.findViewById(R.id.home_preview) + // private val previewHeader: FrameLayout = itemView.findViewById(R.id.home_preview) private var resumeHolder: View = itemView.findViewById(R.id.home_watch_holder) private var resumeRecyclerView: RecyclerView = itemView.findViewById(R.id.home_watch_child_recyclerview) @@ -257,7 +257,7 @@ class HomeParentItemAdapterPreview( private var homeAccount: View? = itemView.findViewById(R.id.home_preview_switch_account) - private var topPadding : View? = itemView.findViewById(R.id.home_padding) + private var topPadding: View? = itemView.findViewById(R.id.home_padding) private val homeNonePadding: View = itemView.findViewById(R.id.home_none_padding) @@ -283,7 +283,11 @@ class HomeParentItemAdapterPreview( item.plot ?: "" homePreviewText.text = item.name - populateChips(homePreviewTags,item.tags ?: emptyList(), R.style.ChipFilledSemiTransparent) + populateChips( + homePreviewTags, + item.tags ?: emptyList(), + R.style.ChipFilledSemiTransparent + ) homePreviewTags.isGone = item.tags.isNullOrEmpty() @@ -413,7 +417,7 @@ class HomeParentItemAdapterPreview( Pair(itemView.findViewById(R.id.home_plan_to_watch_btt), WatchType.PLANTOWATCH), ) - private val toggleListHolder : ChipGroup? = itemView.findViewById(R.id.home_type_holder) + private val toggleListHolder: ChipGroup? = itemView.findViewById(R.id.home_type_holder) init { previewViewpager.setPageTransformer(HomeScrollTransformer()) @@ -422,8 +426,14 @@ class HomeParentItemAdapterPreview( resumeRecyclerView.adapter = resumeAdapter bookmarkRecyclerView.adapter = bookmarkAdapter - resumeRecyclerView.setLinearListLayout(nextLeft = R.id.nav_rail_view, nextRight = FOCUS_SELF) - bookmarkRecyclerView.setLinearListLayout(nextLeft = R.id.nav_rail_view, nextRight = FOCUS_SELF) + resumeRecyclerView.setLinearListLayout( + nextLeft = R.id.nav_rail_view, + nextRight = FOCUS_SELF + ) + bookmarkRecyclerView.setLinearListLayout( + nextLeft = R.id.nav_rail_view, + nextRight = FOCUS_SELF + ) fixPaddingStatusbarMargin(topPadding) @@ -547,7 +557,10 @@ class HomeParentItemAdapterPreview( resumeWatching, false ), 1, false - ) + ), + deleteCallback = { + viewModel.deleteResumeWatching() + } ) } } @@ -572,7 +585,9 @@ class HomeParentItemAdapterPreview( list, false ), 1, false - ) + ), deleteCallback = { + viewModel.deleteBookmarks(list) + } ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index e8cf8863..b27223ec 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -41,6 +41,8 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteAllBookmarkedData +import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteAllResumeStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData @@ -92,6 +94,21 @@ class HomeViewModel : ViewModel() { } } + fun deleteResumeWatching() { + deleteAllResumeStateIds() + loadResumeWatching() + } + + fun deleteBookmarks(list: List) { + list.forEach { DataStoreHelper.deleteBookmarkedData(it.id) } + loadStoredData() + } + + fun deleteBookmarks() { + deleteAllBookmarkedData() + loadStoredData() + } + var repo: APIRepository? = null private val _apiName = MutableLiveData() @@ -394,11 +411,14 @@ class HomeViewModel : ViewModel() { } - private val _popup = MutableLiveData(null) - val popup: LiveData = _popup + private val _popup = MutableLiveData Unit)?>?>(null) + val popup: LiveData Unit)?>?> = _popup - fun popup(list: ExpandableHomepageList?) { - _popup.postValue(list) + fun popup(list: ExpandableHomepageList?, deleteCallback: (() -> Unit)? = null) { + if (list == null) + _popup.postValue(null) + else + _popup.postValue(list to deleteCallback) } private fun bookmarksUpdated(unused: Boolean) { @@ -436,8 +456,7 @@ class HomeViewModel : ViewModel() { // do nothing } - fun reloadStored() { - loadResumeWatching() + fun loadStoredData() { val list = EnumSet.noneOf(WatchType::class.java) getKey(HOME_BOOKMARK_VALUE_LIST)?.map { WatchType.fromInternalId(it) }?.let { list.addAll(it) @@ -445,6 +464,11 @@ class HomeViewModel : ViewModel() { loadStoredData(list) } + fun reloadStored() { + loadResumeWatching() + loadStoredData() + } + fun click(load: LoadClickCallback) { loadResult(load.response.url, load.response.apiName, load.action) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt index 41326eb8..96593769 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt @@ -25,6 +25,7 @@ import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_T import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_UNIXTIME_KEY import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_USER_KEY import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi.Companion.OPEN_SUBTITLES_USER_KEY +import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs @@ -143,66 +144,26 @@ object BackupUtils { } @SuppressLint("SimpleDateFormat") - fun FragmentActivity.backup() { + fun FragmentActivity.backup() = ioSafe { var fileStream: OutputStream? = null var printStream: PrintWriter? = null try { if (!checkWrite()) { - showToast(getString(R.string.backup_failed), Toast.LENGTH_LONG) + showToast(R.string.backup_failed, Toast.LENGTH_LONG) requestRW() - return + return@ioSafe } val date = SimpleDateFormat("yyyy_MM_dd_HH_mm").format(Date(currentTimeMillis())) - val ext = "json" + val ext = "txt" val displayName = "CS3_Backup_${date}" val backupFile = getBackup() - val stream = setupStream(this, displayName, null, ext, false) + val stream = setupStream(this@backup, displayName, null, ext, false) fileStream = stream.openNew() printStream = PrintWriter(fileStream) printStream.print(mapper.writeValueAsString(backupFile)) - /*val steam = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q - && subDir?.isDownloadDir() == true - ) { - val cr = this.contentResolver - val contentUri = - MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) // USE INSTEAD OF MediaStore.Downloads.EXTERNAL_CONTENT_URI - //val currentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - - val newFile = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) - put(MediaStore.MediaColumns.TITLE, displayName) - // While it a json file we store as txt because not - // all file managers support mimetype json - put(MediaStore.MediaColumns.MIME_TYPE, "text/plain") - //put(MediaStore.MediaColumns.RELATIVE_PATH, folder) - } - - val newFileUri = cr.insert( - contentUri, - newFile - ) ?: throw IOException("Error creating file uri") - cr.openOutputStream(newFileUri, "w") - ?: throw IOException("Error opening stream") - } else { - val fileName = "$displayName.$ext" - val rFile = subDir?.findFile(fileName) - if (rFile?.exists() == true) { - rFile.delete() - } - val file = - subDir?.createFile(fileName) - ?: throw IOException("Error creating file") - if (!file.exists()) throw IOException("File does not exist") - file.openOutputStream() - } - - val printStream = PrintWriter(steam) - printStream.print(mapper.writeValueAsString(backupFile)) - printStream.close()*/ - showToast( R.string.backup_success, Toast.LENGTH_LONG @@ -211,7 +172,7 @@ object BackupUtils { logError(e) try { showToast( - getString(R.string.backup_failed_error_format).format(e.toString()), + txt(R.string.backup_failed_error_format, e.toString()), Toast.LENGTH_LONG ) } catch (e: Exception) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 991651dc..2eb2ab01 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -353,6 +353,12 @@ object DataStoreHelper { removeKeys(folder2) } + fun deleteBookmarkedData(id : Int?) { + if (id == null) return + removeKey("$currentAccount/$RESULT_WATCH_STATE", id.toString()) + removeKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) + } + fun getAllResumeStateIds(): List? { val folder = "$currentAccount/$RESULT_RESUME_WATCHING" return getKeys(folder)?.mapNotNull { @@ -519,12 +525,10 @@ object DataStoreHelper { fun setResultWatchState(id: Int?, status: Int) { if (id == null) return - val folder = "$currentAccount/$RESULT_WATCH_STATE" if (status == WatchType.NONE.internalId) { - removeKey(folder, id.toString()) - removeKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) + deleteBookmarkedData(id) } else { - setKey(folder, id.toString(), status) + setKey("$currentAccount/$RESULT_WATCH_STATE", id.toString(), status) } } From ce1f48978be3aada3e6b68d8726539375c3f14f1 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Mon, 28 Aug 2023 20:56:58 +0200 Subject: [PATCH 13/18] fixed download error --- app/build.gradle.kts | 2 +- .../cloudstream3/extractors/SpeedoStream.kt | 13 +++++++++---- .../lagradost/cloudstream3/utils/ExtractorApi.kt | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 333fbfb8..3927d081 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -233,7 +233,7 @@ dependencies { // To fix SSL fuckery on android 9 implementation("org.conscrypt:conscrypt-android:2.2.1") // Util to skip the URI file fuckery 🙏 - implementation("com.github.LagradOst:SafeFile:0.0.2") + implementation("com.github.LagradOst:SafeFile:0.0.3") // API because cba maintaining it myself implementation("com.uwetrottmann.tmdb2:tmdb-java:2.6.0") diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt index 3f6fff2f..213ecdf3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt @@ -7,15 +7,22 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper +class SpeedoStream2 : SpeedoStream() { + override val mainUrl = "https://speedostream.mom" +} + class SpeedoStream1 : SpeedoStream() { override val mainUrl = "https://speedostream.pm" } open class SpeedoStream : ExtractorApi() { override val name = "SpeedoStream" - override val mainUrl = "https://speedostream.mom" + override val mainUrl = "https://speedostream.bond" override val requiresReferer = true + // .bond, .pm, .mom redirect to .bond + private val hostUrl = "https://speedostream.bond" + override suspend fun getUrl(url: String, referer: String?): List { val sources = mutableListOf() app.get(url, referer = referer).document.select("script").map { script -> @@ -26,7 +33,7 @@ open class SpeedoStream : ExtractorApi() { M3u8Helper.generateM3u8( name, it.file, - "$mainUrl/", + "$hostUrl/", ).forEach { m3uData -> sources.add(m3uData) } } } @@ -37,6 +44,4 @@ open class SpeedoStream : ExtractorApi() { private data class File( @JsonProperty("file") val file: String, ) - - } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 83c61542..ffda32d7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -389,6 +389,7 @@ val extractorApis: MutableList = arrayListOf( Acefile(), SpeedoStream(), SpeedoStream1(), + SpeedoStream2(), Zorofile(), Embedgram(), Mvidoo(), From 6089cbc48493d746a110bba8d9997c3d2209b82e Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Wed, 30 Aug 2023 00:52:34 +0200 Subject: [PATCH 14/18] fixed subs on downloads --- app/build.gradle.kts | 2 +- .../ui/player/DownloadFileGenerator.kt | 73 +++++++++++-------- .../utils/VideoDownloadManager.kt | 2 +- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3927d081..55d0f7ae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -233,7 +233,7 @@ dependencies { // To fix SSL fuckery on android 9 implementation("org.conscrypt:conscrypt-android:2.2.1") // Util to skip the URI file fuckery 🙏 - implementation("com.github.LagradOst:SafeFile:0.0.3") + implementation("com.github.LagradOst:SafeFile:0.0.5") // API because cba maintaining it myself implementation("com.uwetrottmann.tmdb2:tmdb-java:2.6.0") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt index baf7ed52..1b618e45 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt @@ -50,6 +50,10 @@ class DownloadFileGenerator( return null } + fun cleanDisplayName(name: String): String { + return name.substringBeforeLast('.').trim() + } + override suspend fun generateLinks( clearCache: Boolean, isCasting: Boolean, @@ -58,39 +62,48 @@ class DownloadFileGenerator( offset: Int, ): Boolean { val meta = episodes[currentIndex + offset] - callback(Pair(null, meta)) + callback(null to meta) - context?.let { ctx -> - val relative = meta.relativePath - val display = meta.displayName + val ctx = context ?: return true + val relative = meta.relativePath ?: return true + val display = meta.displayName ?: return true - if (display == null || relative == null) { - return@let + val cleanDisplay = cleanDisplayName(display) + + VideoDownloadManager.getFolder(ctx, relative, meta.basePath) + ?.forEach { (name, uri) -> + // only these files are allowed, so no videos as subtitles + if (listOf( + ".vtt", + ".srt", + ".txt", + ".ass", + ".ttml", + ".sbv", + ".dfxp" + ).none { name.contains(it, true) } + ) return@forEach + + // cant have the exact same file as a subtitle + if (name.equals(display, true)) return@forEach + + val cleanName = cleanDisplayName(name) + + // we only want files with the approx same name + if (!cleanName.startsWith(cleanDisplay, true)) return@forEach + + val realName = cleanName.removePrefix(cleanDisplay) + + subtitleCallback( + SubtitleData( + realName.ifBlank { ctx.getString(R.string.default_subtitles) }, + uri.toString(), + SubtitleOrigin.DOWNLOADED_FILE, + name.toSubtitleMimeType(), + emptyMap() + ) + ) } - VideoDownloadManager.getFolder(ctx, relative, meta.basePath) - ?.forEach { file -> - val name = display.removeSuffix(".mp4") - if (file.first != meta.displayName && file.first.startsWith(name)) { - val realName = file.first.removePrefix(name) - .removeSuffix(".vtt") - .removeSuffix(".srt") - .removeSuffix(".txt") - .trim() - .removePrefix("(") - .removeSuffix(")") - - subtitleCallback( - SubtitleData( - realName.ifBlank { ctx.getString(R.string.default_subtitles) }, - file.second.toString(), - SubtitleOrigin.DOWNLOADED_FILE, - name.toSubtitleMimeType(), - emptyMap() - ) - ) - } - } - } return true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 6425ba66..72eb002a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -505,7 +505,7 @@ object VideoDownloadManager { ): List>? { val base = basePathToFile(context, basePath) val folder = base?.gotoDirectory(relativePath, false) ?: return null - if (folder.isDirectory() != false) return null + //if (folder.isDirectory() != false) return null return folder.listFiles() ?.mapNotNull { (it.name() ?: "") to (it.uri() ?: return@mapNotNull null) } From 9c991f2abd53bdbf50f7ae52f3fe64221d341e57 Mon Sep 17 00:00:00 2001 From: Sofie <117321707+Sofie99@users.noreply.github.com> Date: Sat, 2 Sep 2023 05:32:18 +0700 Subject: [PATCH 15/18] extractor: fix chillx (#583) * Extractor: added Rabbitstream * Extractor: added Rabbitstream * extractor: fix Chillx * comply --------- Co-authored-by: Sofie99 --- .../cloudstream3/extractors/Chillx.kt | 58 +---------- .../cloudstream3/extractors/Gdriveplayer.kt | 88 +---------------- .../extractors/helper/AesHelper.kt | 95 +++++++++++++++++++ 3 files changed, 102 insertions(+), 139 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Chillx.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Chillx.kt index b4f3d897..bcf8848c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Chillx.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Chillx.kt @@ -2,15 +2,12 @@ package com.lagradost.cloudstream3.extractors import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.extractors.helper.* +import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities -import javax.crypto.Cipher -import javax.crypto.SecretKeyFactory -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.PBEKeySpec -import javax.crypto.spec.SecretKeySpec class Moviesapi : Chillx() { override val name = "Moviesapi" @@ -32,7 +29,7 @@ open class Chillx : ExtractorApi() { override val requiresReferer = true companion object { - private const val KEY = "11x&W5UBrcqn\$9Yl" + private const val KEY = "m4H6D9%0\$N&F6rQ&" } override suspend fun getUrl( @@ -47,8 +44,7 @@ open class Chillx : ExtractorApi() { referer = referer ).text )?.groupValues?.get(1) - val encData = AppUtils.tryParseJson(base64Decode(master ?: return)) - val decrypt = cryptoAESHandler(encData ?: return, KEY, false) + val decrypt = cryptoAESHandler(master ?: return, KEY.toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt") val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1) val tracks = Regex("""tracks:\s*\[(.+)]""").find(decrypt)?.groupValues?.get(1) @@ -86,52 +82,6 @@ open class Chillx : ExtractorApi() { } } - private fun cryptoAESHandler( - data: AESData, - pass: String, - encrypt: Boolean = true - ): String { - val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512") - val spec = PBEKeySpec( - pass.toCharArray(), - data.salt?.hexToByteArray(), - data.iterations?.toIntOrNull() ?: 1, - 256 - ) - val key = factory.generateSecret(spec) - val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - return if (!encrypt) { - cipher.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(key.encoded, "AES"), - IvParameterSpec(data.iv?.hexToByteArray()) - ) - String(cipher.doFinal(base64DecodeArray(data.ciphertext.toString()))) - } else { - cipher.init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(key.encoded, "AES"), - IvParameterSpec(data.iv?.hexToByteArray()) - ) - base64Encode(cipher.doFinal(data.ciphertext?.toByteArray())) - } - } - - private fun String.hexToByteArray(): ByteArray { - check(length % 2 == 0) { "Must have an even length" } - return chunked(2) - .map { it.toInt(16).toByte() } - - .toByteArray() - } - - data class AESData( - @JsonProperty("ciphertext") val ciphertext: String? = null, - @JsonProperty("iv") val iv: String? = null, - @JsonProperty("salt") val salt: String? = null, - @JsonProperty("iterations") val iterations: String? = null, - ) - data class Tracks( @JsonProperty("file") val file: String? = null, @JsonProperty("label") val label: String? = null, diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt index df9c74a4..8d1a4d07 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt @@ -2,14 +2,10 @@ package com.lagradost.cloudstream3.extractors import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import org.jsoup.nodes.Element -import java.security.DigestException -import java.security.MessageDigest -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec class DatabaseGdrive2 : Gdriveplayer() { override var mainUrl = "https://databasegdriveplayer.co" @@ -65,78 +61,6 @@ open class Gdriveplayer : ExtractorApi() { ?.data()?.let { getAndUnpack(it) } } - private fun String.decodeHex(): ByteArray { - check(length % 2 == 0) { "Must have an even length" } - return chunked(2) - .map { it.toInt(16).toByte() } - .toByteArray() - } - - // https://stackoverflow.com/a/41434590/8166854 - private fun GenerateKeyAndIv( - password: ByteArray, - salt: ByteArray, - hashAlgorithm: String = "MD5", - keyLength: Int = 32, - ivLength: Int = 16, - iterations: Int = 1 - ): List? { - - val md = MessageDigest.getInstance(hashAlgorithm) - val digestLength = md.digestLength - val targetKeySize = keyLength + ivLength - val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength - val generatedData = ByteArray(requiredLength) - var generatedLength = 0 - - try { - md.reset() - - while (generatedLength < targetKeySize) { - if (generatedLength > 0) - md.update( - generatedData, - generatedLength - digestLength, - digestLength - ) - - md.update(password) - md.update(salt, 0, 8) - md.digest(generatedData, generatedLength, digestLength) - - for (i in 1 until iterations) { - md.update(generatedData, generatedLength, digestLength) - md.digest(generatedData, generatedLength, digestLength) - } - - generatedLength += digestLength - } - return listOf( - generatedData.copyOfRange(0, keyLength), - generatedData.copyOfRange(keyLength, targetKeySize) - ) - } catch (e: DigestException) { - return null - } - } - - private fun cryptoAESHandler( - data: AesData, - pass: ByteArray, - encrypt: Boolean = true - ): String? { - val (key, iv) = GenerateKeyAndIv(pass, data.s.decodeHex()) ?: return null - val cipher = Cipher.getInstance("AES/CBC/NoPadding") - return if (!encrypt) { - cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) - String(cipher.doFinal(base64DecodeArray(data.ct))) - } else { - cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) - base64Encode(cipher.doFinal(data.ct.toByteArray())) - - } - } - private fun Regex.first(str: String): String? { return find(str)?.groupValues?.getOrNull(1) } @@ -154,14 +78,14 @@ open class Gdriveplayer : ExtractorApi() { val document = app.get(url).document val eval = unpackJs(document)?.replace("\\", "") ?: return - val data = tryParseJson(Regex("data='(\\S+?)'").first(eval)) ?: return + val data = Regex("data='(\\S+?)'").first(eval) ?: return val password = Regex("null,['|\"](\\w+)['|\"]").first(eval) ?.split(Regex("\\D+")) ?.joinToString("") { Char(it.toInt()).toString() }.let { Regex("var pass = \"(\\S+?)\"").first(it ?: return)?.toByteArray() } ?: throw ErrorLoadingException("can't find password") - val decryptedData = cryptoAESHandler(data, password, false)?.let { getAndUnpack(it) }?.replace("\\", "") + val decryptedData = cryptoAESHandler(data, password, false, "AES/CBC/NoPadding")?.let { getAndUnpack(it) }?.replace("\\", "") val sourceData = decryptedData?.substringAfter("sources:[")?.substringBefore("],") val subData = decryptedData?.substringAfter("tracks:[")?.substringBefore("],") @@ -194,12 +118,6 @@ open class Gdriveplayer : ExtractorApi() { } - data class AesData( - @JsonProperty("ct") val ct: String, - @JsonProperty("iv") val iv: String, - @JsonProperty("s") val s: String - ) - data class Tracks( @JsonProperty("file") val file: String, @JsonProperty("kind") val kind: String, diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt new file mode 100644 index 00000000..b41eae52 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AesHelper.kt @@ -0,0 +1,95 @@ +package com.lagradost.cloudstream3.extractors.helper + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.ErrorLoadingException +import com.lagradost.cloudstream3.base64DecodeArray +import com.lagradost.cloudstream3.base64Encode +import com.lagradost.cloudstream3.utils.AppUtils +import java.security.DigestException +import java.security.MessageDigest +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +object AesHelper { + + private const val HASH = "AES/CBC/PKCS5PADDING" + private const val KDF = "MD5" + + fun cryptoAESHandler( + data: String, + pass: ByteArray, + encrypt: Boolean = true, + padding: String = HASH, + ): String? { + val parse = AppUtils.tryParseJson(data) ?: return null + val (key, iv) = generateKeyAndIv(pass, parse.s.hexToByteArray()) ?: return null + val cipher = Cipher.getInstance(padding) + return if (!encrypt) { + cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) + String(cipher.doFinal(base64DecodeArray(parse.ct))) + } else { + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) + base64Encode(cipher.doFinal(parse.ct.toByteArray())) + } + } + + // https://stackoverflow.com/a/41434590/8166854 + fun generateKeyAndIv( + password: ByteArray, + salt: ByteArray, + hashAlgorithm: String = KDF, + keyLength: Int = 32, + ivLength: Int = 16, + iterations: Int = 1 + ): Pair? { + + val md = MessageDigest.getInstance(hashAlgorithm) + val digestLength = md.digestLength + val targetKeySize = keyLength + ivLength + val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength + val generatedData = ByteArray(requiredLength) + var generatedLength = 0 + + try { + md.reset() + + while (generatedLength < targetKeySize) { + if (generatedLength > 0) + md.update( + generatedData, + generatedLength - digestLength, + digestLength + ) + + md.update(password) + md.update(salt, 0, 8) + md.digest(generatedData, generatedLength, digestLength) + + for (i in 1 until iterations) { + md.update(generatedData, generatedLength, digestLength) + md.digest(generatedData, generatedLength, digestLength) + } + + generatedLength += digestLength + } + return generatedData.copyOfRange(0, keyLength) to generatedData.copyOfRange(keyLength, targetKeySize) + } catch (e: DigestException) { + return null + } + } + + fun String.hexToByteArray(): ByteArray { + check(length % 2 == 0) { "Must have an even length" } + return chunked(2) + .map { it.toInt(16).toByte() } + .toByteArray() + } + + private data class AesData( + @JsonProperty("ct") val ct: String, + @JsonProperty("iv") val iv: String, + @JsonProperty("s") val s: String + ) + +} \ No newline at end of file From 6211b02e85b0dcd4ab5a2954623917e8c27ba552 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Sun, 3 Sep 2023 23:32:43 +0200 Subject: [PATCH 16/18] switched from isM3u8 to ExtractorLinkType --- .../cloudstream3/extractors/Pelisplus.kt | 3 +- .../cloudstream3/extractors/Vidstream.kt | 3 +- .../cloudstream3/extractors/WcoStream.kt | 4 +- .../cloudstream3/extractors/Wibufile.kt | 6 +- .../cloudstream3/ui/APIRepository.kt | 14 ++- .../cloudstream3/ui/ControllerActivity.kt | 4 +- .../cloudstream3/ui/player/CS3IPlayer.kt | 12 +- .../ui/player/DownloadFileGenerator.kt | 4 +- .../ui/player/ExtractorLinkGenerator.kt | 7 +- .../cloudstream3/ui/player/IGenerator.kt | 43 ++++++- .../cloudstream3/ui/player/LinkGenerator.kt | 2 +- .../ui/player/PlayerGeneratorViewModel.kt | 12 +- .../ui/player/RepoLinkGenerator.kt | 21 ++-- .../ui/result/ResultViewModel2.kt | 40 +++--- .../cloudstream3/utils/CastHelper.kt | 6 +- .../cloudstream3/utils/ExtractorApi.kt | 119 +++++++++++++++--- .../utils/VideoDownloadManager.kt | 78 +++++++----- 17 files changed, 269 insertions(+), 109 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt index 45ec4c2f..4163cd94 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt @@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.loadExtractor @@ -66,7 +67,7 @@ open class Pelisplus(val mainUrl: String) { href, page.url, getQualityFromName(qual), - element.attr("href").contains(".m3u8") + type = INFER_TYPE ) ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt index 7eb7fbac..c6493dbe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt @@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.argamap import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.loadExtractor @@ -70,7 +71,7 @@ class Vidstream(val mainUrl: String) { href, page.url, getQualityFromName(qual), - element.attr("href").contains(".m3u8") + type = INFER_TYPE ) ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt index 6cc486cd..659d7804 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt @@ -7,6 +7,7 @@ import com.lagradost.cloudstream3.extractors.helper.NineAnimeHelper.encrypt import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.Qualities class Vidstreamz : WcoStream() { @@ -126,8 +127,7 @@ open class WcoStream : ExtractorApi() { if (!response.text.startsWith("{")) throw ErrorLoadingException("Seems like 9Anime kiddies changed stuff again, Go touch some grass for bout an hour Or use a different Server") return response.parsed().data.media.sources.map { - ExtractorLink(name, it.file,it.file,host,Qualities.Unknown.value,it.file.contains(".m3u8")) + ExtractorLink(name, it.file, it.file, host, Qualities.Unknown.value, type = INFER_TYPE) } - } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Wibufile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Wibufile.kt index ae1e872a..c69f0938 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Wibufile.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Wibufile.kt @@ -4,8 +4,8 @@ import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.Qualities -import java.net.URI open class Wibufile : ExtractorApi() { override val name: String = "Wibufile" @@ -28,10 +28,8 @@ open class Wibufile : ExtractorApi() { video ?: return, "$mainUrl/", Qualities.Unknown.value, - URI(url).path.endsWith(".m3u8") + type = INFER_TYPE ) ) - } - } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt index 4ab2e8e2..a075cc2e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt @@ -1,16 +1,24 @@ package com.lagradost.cloudstream3.ui -import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTimeMS +import com.lagradost.cloudstream3.DubStatus +import com.lagradost.cloudstream3.ErrorLoadingException +import com.lagradost.cloudstream3.HomePageResponse +import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent +import com.lagradost.cloudstream3.MainPageRequest +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.fixUrl import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.ExtractorLink import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope.coroutineContext import kotlinx.coroutines.async import kotlinx.coroutines.delay @@ -174,7 +182,7 @@ class APIRepository(val api: MainAPI) { data: String, isCasting: Boolean, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ): Boolean { if (isInvalidData(data)) return false // this makes providers cleaner return try { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt index 46ddce09..6c0e7796 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt @@ -25,6 +25,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.sortSubs import com.lagradost.cloudstream3.sortUrls +import com.lagradost.cloudstream3.ui.player.LoadType import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.result.ResultEpisode @@ -294,7 +295,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi val generator = RepoLinkGenerator(listOf(epData)) val isSuccessful = safeApiCall { - generator.generateLinks(clearCache = false, isCasting = true, + generator.generateLinks( + clearCache = false, type = LoadType.Chromecast, callback = { it.first?.let { link -> currentLinks.add(link) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 2067eb04..fd1da5ca 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -53,9 +53,11 @@ import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.EpisodeSkip import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList +import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import java.io.File +import java.lang.IllegalArgumentException import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession @@ -1257,10 +1259,12 @@ class CS3IPlayer : IPlayer { HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory) } - val mime = when { - link.isM3u8 -> MimeTypes.APPLICATION_M3U8 - link.isDash -> MimeTypes.APPLICATION_MPD - else -> MimeTypes.VIDEO_MP4 + val mime = when(link.type) { + ExtractorLinkType.M3U8 -> MimeTypes.APPLICATION_M3U8 + ExtractorLinkType.DASH -> MimeTypes.APPLICATION_MPD + ExtractorLinkType.VIDEO -> MimeTypes.VIDEO_MP4 + ExtractorLinkType.TORRENT -> throw IllegalArgumentException("No torrent support") + ExtractorLinkType.MAGNET -> throw IllegalArgumentException("No magnet support") } val mediaItems = if (link is ExtractorLinkPlayList) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt index 1b618e45..b0223bb5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt @@ -56,10 +56,10 @@ class DownloadFileGenerator( override suspend fun generateLinks( clearCache: Boolean, - isCasting: Boolean, + type: LoadType, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int, + offset: Int ): Boolean { val meta = episodes[currentIndex + offset] callback(null to meta) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt index 7c19e97d..d8d2d537 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt @@ -37,14 +37,17 @@ class ExtractorLinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - isCasting: Boolean, + type: LoadType, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, offset: Int ): Boolean { subtitles.forEach(subtitleCallback) + val allowedTypes = type.toSet() links.forEach { - callback.invoke(it to null) + if(allowedTypes.contains(it.type)) { + callback.invoke(it to null) + } } return true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt index a1287e6a..af74cb57 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt @@ -1,8 +1,43 @@ package com.lagradost.cloudstream3.ui.player import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.ExtractorUri +enum class LoadType { + Unknown, + InApp, + InAppDownload, + ExternalApp, + Browser, + Chromecast +} + +fun LoadType.toSet() : Set { + return when(this) { + LoadType.InApp -> setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + LoadType.Browser -> setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + LoadType.InAppDownload -> setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.M3U8 + ) + LoadType.ExternalApp, LoadType.Unknown -> ExtractorLinkType.values().toSet() + LoadType.Chromecast -> setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + } +} + interface IGenerator { val hasCache: Boolean @@ -13,15 +48,15 @@ interface IGenerator { fun goto(index: Int) fun getCurrentId(): Int? // this is used to save data or read data about this id - fun getCurrent(offset : Int = 0): Any? // this is used to get metadata about the current playing, can return null - fun getAll() : List? // this us used to get the metadata about all entries, not needed + fun getCurrent(offset: Int = 0): Any? // this is used to get metadata about the current playing, can return null + fun getAll(): List? // this us used to get the metadata about all entries, not needed /* not safe, must use try catch */ suspend fun generateLinks( clearCache: Boolean, - isCasting: Boolean, + type: LoadType, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset : Int = 0, + offset: Int = 0, ): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt index 0b560857..ba2cdb40 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt @@ -48,7 +48,7 @@ class LinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - isCasting: Boolean, + type: LoadType, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, offset: Int diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index 1b13b519..42659f8d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -78,10 +78,10 @@ class PlayerGeneratorViewModel : ViewModel() { if (generator?.hasCache == true && generator?.hasNext() == true) { safeApiCall { generator?.generateLinks( + type = LoadType.InApp, clearCache = false, - isCasting = false, - {}, - {}, + callback = {}, + subtitleCallback = {}, offset = 1 ) } @@ -147,7 +147,7 @@ class PlayerGeneratorViewModel : ViewModel() { } } - fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) { + fun loadLinks(clearCache: Boolean = false, type: LoadType = LoadType.InApp) { Log.i(TAG, "loadLinks") currentJob?.cancel() @@ -162,14 +162,14 @@ class PlayerGeneratorViewModel : ViewModel() { // load more data _loadingLinks.postValue(Resource.Loading()) val loadingState = safeApiCall { - generator?.generateLinks(clearCache = clearCache, isCasting = isCasting, { + generator?.generateLinks(type = type,clearCache = clearCache, callback = { currentLinks.add(it) // Clone to prevent ConcurrentModificationException normalSafeApiCall { // Extra normalSafeApiCall since .toSet() iterates. _currentLinks.postValue(currentLinks.toSet()) } - }, { + }, subtitleCallback = { currentSubs.add(it) normalSafeApiCall { _currentSubs.postValue(currentSubs.toSet()) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt index 2ce53ea5..d55da57c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt @@ -67,18 +67,19 @@ class RepoLinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - isCasting: Boolean, + type: LoadType, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int, + offset: Int ): Boolean { + val allowedTypes = type.toSet() val index = currentIndex val current = episodes.getOrNull(index + offset) ?: return false val (currentLinkCache, currentSubsCache) = if (clearCache) { Pair(mutableSetOf(), mutableSetOf()) } else { - cache[Pair(current.apiName, current.id)] ?: Pair(mutableSetOf(), mutableSetOf()) + cache[current.apiName to current.id] ?: Pair(mutableSetOf(), mutableSetOf()) } //val currentLinkCache = if (clearCache) mutableSetOf() else linkCache[index].toMutableSet() @@ -88,9 +89,9 @@ class RepoLinkGenerator( val currentSubsUrls = mutableSetOf() // makes all subs urls unique val currentSubsNames = mutableSetOf() // makes all subs names unique - currentLinkCache.forEach { link -> + currentLinkCache.filter { allowedTypes.contains(it.type) }.forEach { link -> currentLinks.add(link.url) - callback(Pair(link, null)) + callback(link to null) } currentSubsCache.forEach { sub -> @@ -108,8 +109,8 @@ class RepoLinkGenerator( val result = APIRepository( getApiFromNameNull(current.apiName) ?: throw Exception("This provider does not exist") ).loadLinks(current.data, - isCasting, - { file -> + isCasting = LoadType.Chromecast == type, + subtitleCallback = { file -> val correctFile = PlayerSubtitleHelper.getSubtitleData(file) if (!currentSubsUrls.contains(correctFile.url)) { currentSubsUrls.add(correctFile.url) @@ -132,12 +133,14 @@ class RepoLinkGenerator( } } }, - { link -> + callback = { link -> Log.d(TAG, "Loaded ExtractorLink: $link") if (!currentLinks.contains(link.url)) { if (!currentLinkCache.contains(link)) { currentLinks.add(link.url) - callback(Pair(link, null)) + if (allowedTypes.contains(link.type)) { + callback(Pair(link, null)) + } currentLinkCache.add(link) //linkCache[index] = currentLinkCache } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 82d9a8fe..b2c57137 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -36,6 +36,7 @@ import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.IGenerator +import com.lagradost.cloudstream3.ui.player.LoadType import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction @@ -745,7 +746,7 @@ class ResultViewModel2 : ViewModel() { val generator = RepoLinkGenerator(listOf(episode)) val currentLinks = mutableSetOf() val currentSubs = mutableSetOf() - generator.generateLinks(clearCache = false, isCasting = false, callback = { + generator.generateLinks(clearCache = false, LoadType.Chromecast, callback = { it.first?.let { link -> currentLinks.add(link) } @@ -825,7 +826,7 @@ class ResultViewModel2 : ViewModel() { isVisible: Boolean = true ) { if (activity == null) return - loadLinks(result, isVisible = isVisible, isCasting = true) { data -> + loadLinks(result, isVisible = isVisible, LoadType.Chromecast) { data -> startChromecast(activity, result, data.links, data.subs, 0) } } @@ -936,7 +937,7 @@ class ResultViewModel2 : ViewModel() { private fun loadLinks( result: ResultEpisode, isVisible: Boolean, - isCasting: Boolean, + type: LoadType, clearCache: Boolean = false, work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit) ) { @@ -945,7 +946,7 @@ class ResultViewModel2 : ViewModel() { val links = loadLinks( result, isVisible = isVisible, - isCasting = isCasting, + type = type, clearCache = clearCache ) if (!this.isActive) return@ioSafe @@ -956,11 +957,11 @@ class ResultViewModel2 : ViewModel() { private var currentLoadLinkJob: Job? = null private fun acquireSingleLink( result: ResultEpisode, - isCasting: Boolean, + type: LoadType, text: UiText, callback: (Pair) -> Unit, ) { - loadLinks(result, isVisible = true, isCasting = isCasting) { links -> + loadLinks(result, isVisible = true, type) { links -> postPopup( text, links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) { @@ -971,11 +972,10 @@ class ResultViewModel2 : ViewModel() { private fun acquireSingleSubtitle( result: ResultEpisode, - isCasting: Boolean, text: UiText, callback: (Pair) -> Unit, ) { - loadLinks(result, isVisible = true, isCasting = isCasting) { links -> + loadLinks(result, isVisible = true, type = LoadType.Unknown) { links -> postPopup( text, links.subs.map { txt(it.name) }) @@ -988,7 +988,7 @@ class ResultViewModel2 : ViewModel() { private suspend fun CoroutineScope.loadLinks( result: ResultEpisode, isVisible: Boolean, - isCasting: Boolean, + type: LoadType, clearCache: Boolean = false, ): LinkLoadingResult { val tempGenerator = RepoLinkGenerator(listOf(result)) @@ -1002,7 +1002,7 @@ class ResultViewModel2 : ViewModel() { } try { updatePage() - tempGenerator.generateLinks(clearCache, isCasting, { (link, _) -> + tempGenerator.generateLinks(clearCache, type, { (link, _) -> if (link != null) { links += link updatePage() @@ -1272,7 +1272,6 @@ class ResultViewModel2 : ViewModel() { acquireSingleSubtitle( click.data, - false, txt(R.string.episode_action_download_subtitle) ) { (links, index) -> downloadSubtitle( @@ -1317,7 +1316,7 @@ class ResultViewModel2 : ViewModel() { val response = currentResponse ?: return acquireSingleLink( click.data, - false, + LoadType.InAppDownload, txt(R.string.episode_action_download_mirror) ) { (result, index) -> ioSafe { @@ -1347,7 +1346,7 @@ class ResultViewModel2 : ViewModel() { loadLinks( click.data, isVisible = false, - isCasting = false, + type = LoadType.InApp, clearCache = true ) } @@ -1356,7 +1355,7 @@ class ResultViewModel2 : ViewModel() { ACTION_CHROME_CAST_MIRROR -> { acquireSingleLink( click.data, - isCasting = true, + LoadType.Chromecast, txt(R.string.episode_action_chromecast_mirror) ) { (result, index) -> startChromecast(activity, click.data, result.links, result.subs, index) @@ -1365,7 +1364,7 @@ class ResultViewModel2 : ViewModel() { ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink( click.data, - isCasting = true, + LoadType.Browser, txt(R.string.episode_action_play_in_browser) ) { (result, index) -> try { @@ -1380,7 +1379,7 @@ class ResultViewModel2 : ViewModel() { ACTION_COPY_LINK -> { acquireSingleLink( click.data, - isCasting = true, + LoadType.ExternalApp, txt(R.string.episode_action_copy_link) ) { (result, index) -> val act = activity ?: return@acquireSingleLink @@ -1399,7 +1398,7 @@ class ResultViewModel2 : ViewModel() { } ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> { - loadLinks(click.data, isVisible = true, isCasting = true) { links -> + loadLinks(click.data, isVisible = true, LoadType.ExternalApp) { links -> if (links.links.isEmpty()) { showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT) return@loadLinks @@ -1415,7 +1414,7 @@ class ResultViewModel2 : ViewModel() { ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink( click.data, - isCasting = true, + LoadType.Chromecast, txt( R.string.episode_action_play_in_format, txt(R.string.player_settings_play_in_web) @@ -1432,7 +1431,7 @@ class ResultViewModel2 : ViewModel() { ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink( click.data, - isCasting = true, + LoadType.Chromecast, txt( R.string.episode_action_play_in_format, txt(R.string.player_settings_play_in_mpv) @@ -1461,7 +1460,6 @@ class ResultViewModel2 : ViewModel() { if (index >= 0) it.goto(index) } - } ?: return, list ) ) @@ -2173,7 +2171,7 @@ class ResultViewModel2 : ViewModel() { trailerData.extractorUrl, trailerData.referer ?: "", Qualities.Unknown.value, - trailerData.extractorUrl.contains(".m3u8") + type = INFER_TYPE ) ) to arrayListOf() } else { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt index 6b5e9ec2..d8373165 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt @@ -55,7 +55,11 @@ object CastHelper { val builder = MediaInfo.Builder(link.url) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) - .setContentType(if (link.isM3u8) MimeTypes.APPLICATION_M3U8 else MimeTypes.VIDEO_MP4) + .setContentType(when(link.type) { + ExtractorLinkType.M3U8 -> MimeTypes.APPLICATION_M3U8 + ExtractorLinkType.DASH -> MimeTypes.APPLICATION_MPD + else -> MimeTypes.VIDEO_MP4 + }) .setMetadata(movieMetadata) .setMediaTracks(tracks) data?.let { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index ffda32d7..2a539f0d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -4,8 +4,10 @@ import android.net.Uri import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.extractors.* +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import kotlinx.coroutines.delay import org.jsoup.Jsoup +import java.net.URL import kotlin.collections.MutableList /** @@ -35,35 +37,101 @@ data class ExtractorLinkPlayList( val playlist: List, override val referer: String, override val quality: Int, - override val isM3u8: Boolean = false, + val isM3u8: Boolean = false, override val headers: Map = mapOf(), /** Used for getExtractorVerifierJob() */ override val extractorData: String? = null, + override val type: ExtractorLinkType, ) : ExtractorLink( - source, - name, - // Blank as un-used - "", - referer, - quality, - isM3u8, - headers, - extractorData -) + source = source, + name = name, + url = "", + referer = referer, + quality = quality, + headers = headers, + extractorData = extractorData, + type = type +) { + constructor( + source: String, + name: String, + playlist: List, + referer: String, + quality: Int, + isM3u8: Boolean = false, + headers: Map = mapOf(), + extractorData: String? = null, + ) : this( + source = source, + name = name, + playlist = playlist, + referer = referer, + quality = quality, + type = if (isM3u8) ExtractorLinkType.M3U8 else ExtractorLinkType.VIDEO, + headers = headers, + extractorData = extractorData, + ) +} +/** Metadata about the file type used for downloads and exoplayer hint, + * if you respond with the wrong one the file will fail to download or be played */ +enum class ExtractorLinkType { + /** Single stream of bytes no matter the actual file type */ + VIDEO, + /** Split into several .ts files, has support for encrypted m3u8s */ + M3U8, + /** Like m3u8 but uses xml, currently no download support */ + DASH, + /** No support at the moment */ + TORRENT, + /** No support at the moment */ + MAGNET, +} +private fun inferTypeFromUrl(url: String): ExtractorLinkType { + val path = normalSafeApiCall { URL(url).path } + return when { + path?.endsWith(".m3u8") == true -> ExtractorLinkType.M3U8 + path?.endsWith(".mpd") == true -> ExtractorLinkType.DASH + path?.endsWith(".torrent") == true -> ExtractorLinkType.TORRENT + url.startsWith("magnet:") -> ExtractorLinkType.MAGNET + else -> ExtractorLinkType.VIDEO + } +} +val INFER_TYPE : ExtractorLinkType? = null open class ExtractorLink constructor( open val source: String, open val name: String, override val url: String, override val referer: String, open val quality: Int, - open val isM3u8: Boolean = false, override val headers: Map = mapOf(), /** Used for getExtractorVerifierJob() */ open val extractorData: String? = null, - open val isDash: Boolean = false, + open val type: ExtractorLinkType, ) : VideoDownloadManager.IDownloadableMinimum { + constructor( + source: String, + name: String, + url: String, + referer: String, + quality: Int, + /** the type of the media, use INFER_TYPE if you want to auto infer the type from the url */ + type: ExtractorLinkType?, + headers: Map = mapOf(), + /** Used for getExtractorVerifierJob() */ + extractorData: String? = null, + ) : this( + source = source, + name = name, + url = url, + referer = referer, + quality = quality, + headers = headers, + extractorData = extractorData, + type = type ?: inferTypeFromUrl(url) + ) + /** * Old constructor without isDash, allows for backwards compatibility with extensions. * Should be removed after all extensions have updated their cloudstream.jar @@ -80,8 +148,30 @@ open class ExtractorLink constructor( extractorData: String? = null ) : this(source, name, url, referer, quality, isM3u8, headers, extractorData, false) + constructor( + source: String, + name: String, + url: String, + referer: String, + quality: Int, + isM3u8: Boolean = false, + headers: Map = mapOf(), + /** Used for getExtractorVerifierJob() */ + extractorData: String? = null, + isDash: Boolean, + ) : this( + source = source, + name = name, + url = url, + referer = referer, + quality = quality, + headers = headers, + extractorData = extractorData, + type = if (isDash) ExtractorLinkType.DASH else if (isM3u8) ExtractorLinkType.M3U8 else ExtractorLinkType.VIDEO + ) + override fun toString(): String { - return "ExtractorLink(name=$name, url=$url, referer=$referer, isM3u8=$isM3u8)" + return "ExtractorLink(name=$name, url=$url, referer=$referer, type=$type)" } } @@ -135,6 +225,7 @@ enum class Qualities(var value: Int, val defaultPriority: Int) { else -> "${qual}p" } } + fun getStringByIntFull(quality: Int): String { return when (quality) { 0 -> "Auto" diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 72eb002a..d108daed 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -53,7 +53,7 @@ import java.io.Closeable import java.io.File import java.io.IOException import java.io.OutputStream -import java.net.URL +import java.lang.IllegalArgumentException import java.util.* const val DOWNLOAD_CHANNEL_ID = "cloudstream3.general" @@ -951,7 +951,10 @@ object VideoDownloadManager { /** how many bytes every connection should be, by default it is 10 MiB */ chuckSize: Long = (1 shl 20) * 10, /** maximum bytes in the buffer that responds */ - bufferSize: Int = DEFAULT_BUFFER_SIZE + bufferSize: Int = DEFAULT_BUFFER_SIZE, + /** how many bytes bytes it should require to use the parallel downloader instead, + * if we download a very small file we don't want it parallel */ + maximumSmallSize : Long = chuckSize * 2 ): LazyStreamDownloadData { // we don't want to make a separate connection for every 1kb require(chuckSize > 1000) @@ -963,7 +966,7 @@ object VideoDownloadManager { var downloadLength: Long? = null var totalLength: Long? = null - val ranges = if (contentLength == null) { + val ranges = if (contentLength == null || contentLength < maximumSmallSize) { // is the equivalent of [startByte..EOF] as we don't know the size we can only do one // connection LongArray(1) { startByte } @@ -1024,6 +1027,7 @@ object VideoDownloadManager { } } + /** download a file that consist of a single stream of data*/ suspend fun downloadThing( context: Context, link: IDownloadableMinimum, @@ -1035,8 +1039,7 @@ object VideoDownloadManager { createNotificationCallback: (CreateNotificationMetadata) -> Unit, parallelConnections: Int = 3 ): DownloadStatus = withContext(Dispatchers.IO) { - // we cant download torrents with this implementation, aria2c might be used in the future - if (link.url.startsWith("magnet") || link.url.endsWith(".torrent") || parallelConnections < 1) { + if (parallelConnections < 1) { return@withContext DOWNLOAD_INVALID_INPUT } @@ -1529,6 +1532,11 @@ object VideoDownloadManager { notificationCallback: (Int, Notification) -> Unit, tryResume: Boolean = false, ): DownloadStatus { + // no support for these file formats + if(link.type == ExtractorLinkType.MAGNET || link.type == ExtractorLinkType.TORRENT || link.type == ExtractorLinkType.DASH) { + return DOWNLOAD_INVALID_INPUT + } + val name = getFileName(context, ep) // Make sure this is cancelled when download is done or cancelled. @@ -1557,35 +1565,39 @@ object VideoDownloadManager { } try { - if (link.isM3u8 || normalSafeApiCall { URL(link.url).path.endsWith(".m3u8") } == true) { - val startIndex = if (tryResume) { - context.getKey( - KEY_DOWNLOAD_INFO, - ep.id.toString(), - null - )?.extraInfo?.toIntOrNull() - } else null + when(link.type) { + ExtractorLinkType.M3U8 -> { + val startIndex = if (tryResume) { + context.getKey( + KEY_DOWNLOAD_INFO, + ep.id.toString(), + null + )?.extraInfo?.toIntOrNull() + } else null - return downloadHLS( - context, - link, - name, - folder ?: "", - ep.id, - startIndex, - callback, parallelConnections = maxConcurrentConnections - ) - } else { - return downloadThing( - context, - link, - name, - folder ?: "", - "mp4", - tryResume, - ep.id, - callback, parallelConnections = maxConcurrentConnections - ) + return downloadHLS( + context, + link, + name, + folder ?: "", + ep.id, + startIndex, + callback, parallelConnections = maxConcurrentConnections + ) + } + ExtractorLinkType.VIDEO -> { + return downloadThing( + context, + link, + name, + folder ?: "", + "mp4", + tryResume, + ep.id, + callback, parallelConnections = maxConcurrentConnections + ) + } + else -> throw IllegalArgumentException("unsuported download type") } } catch (t: Throwable) { return DOWNLOAD_FAILED From cbebafb7b9271780c29b208a60637d7d0675a3a9 Mon Sep 17 00:00:00 2001 From: CranberrySoup <142951702+CranberrySoup@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:36:30 +0000 Subject: [PATCH 17/18] Update sdk version --- app/build.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 55d0f7ae..825d0c4b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,13 +50,13 @@ android { } } - compileSdk = 33 - buildToolsVersion = "30.0.3" + compileSdk = 34 + buildToolsVersion = "34.0.0" defaultConfig { applicationId = "com.lagradost.cloudstream3" minSdk = 21 - targetSdk = 29 + targetSdk = 34 versionCode = 59 versionName = "4.1.8" From 3c3ca21728a9ee094296594f8fa1c3a435a9176a Mon Sep 17 00:00:00 2001 From: CranberrySoup <142951702+CranberrySoup@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:42:33 +0000 Subject: [PATCH 18/18] Let's not be too radical --- app/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 825d0c4b..e31de078 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,13 +50,13 @@ android { } } - compileSdk = 34 + compileSdk = 33 buildToolsVersion = "34.0.0" defaultConfig { applicationId = "com.lagradost.cloudstream3" minSdk = 21 - targetSdk = 34 + targetSdk = 33 versionCode = 59 versionName = "4.1.8"