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"