Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# CloudStream

⚠️ **DISCLAIMER: This application is an EXPERIMENT.**
**The developer is NOT responsible for any improper or dangerous use of this application.**
⛔ **Usage while driving is STRICTLY PROHIBITED.**

**Always prioritize safe driving and adhere to local traffic laws.**
**This software is provided "as is", without warranty of any kind.**


**⚠️ Warning: By default, this app doesn't provide any video sources; you have to install extensions to add functionality to the app.**

[![Discord](https://invidget.switchblade.xyz/5Hus6fM)](https://discord.gg/5Hus6fM)
Expand Down
22 changes: 21 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,20 @@ android {
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = 67
versionName = "4.6.2"
versionName = "4.6.17"

resValue("string", "commit_hash", getGitCommitHash())

resourceConfigurations.addAll(
listOf(
"en", "af", "am", "apc", "ar", "ars", "as", "az", "be", "bg", "bn", "ca", "ckb", "cs",
"de", "el", "eo", "es", "fa", "fil", "fr", "gl", "hi", "hr", "hu", "in", "it", "iw",
"ja", "kn", "ko", "lt", "lv", "mk", "ml", "ms", "mt", "my", "ne", "nl", "nn", "no",
"or", "pl", "pt", "pt-rBR", "ro", "ru", "sk", "so", "sv", "ta", "ti", "tl", "tr",
"uk", "ur", "vi", "zh", "zh-rTW"
)
)


manifestPlaceholders["target_sdk_version"] = libs.versions.targetSdk.get()

Expand Down Expand Up @@ -152,7 +163,15 @@ android {
resValues = true
}



namespace = "com.lagradost.cloudstream3"

sourceSets {
getByName("main") {
res.srcDirs("src/main/res", "src/main/res-car")
}
}
}

dependencies {
Expand All @@ -171,6 +190,7 @@ dependencies {
implementation(libs.fragment.ktx)
implementation(libs.bundles.lifecycle)
implementation(libs.bundles.navigation)
implementation(libs.car.app)

// Design & UI
implementation(libs.preference.ktx)
Expand Down
31 changes: 28 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!-- Used for app update -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Used for app notifications on Android 13+ -->
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> <!-- Used for Android TV watch next -->
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" /> <!-- Used for updates without prompt -->

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- Used for update service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
Expand All @@ -22,6 +22,10 @@
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />

<!-- Android Auto permissions -->
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="androidx.car.app.NAVIGATION_TEMPLATES" tools:ignore="ProtectedPermissions"/>

<!-- Fixes android tv fuckery -->
<uses-feature
android:name="android.hardware.touchscreen"
Expand All @@ -30,6 +34,10 @@
android:name="android.software.leanback"
android:required="false" />



<uses-permission android:name="android.permission.WAKE_LOCK" />

<!-- Without the large heap Exoplayer buffering gets reset due to OOM. -->
<!--TODO https://stackoverflow.com/questions/41799732/chromecast-button-not-visible-in-android-->
<application
Expand All @@ -49,10 +57,18 @@
android:usesCleartextTraffic="true"
tools:targetApi="${target_sdk_version}">

<meta-data
android:name="androidx.car.app.minCarApiLevel"
android:value="1" />

<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.lagradost.cloudstream3.utils.CastOptionsProvider" />

<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />

<profileable
android:shell="true"
tools:targetApi="q" />
Expand Down Expand Up @@ -111,7 +127,7 @@
-->
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation|uiMode"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation|uiMode|locale|layoutDirection"
android:exported="true"
android:launchMode="singleTask"
android:resizeableActivity="true"
Expand Down Expand Up @@ -188,7 +204,7 @@

<activity
android:name=".ui.account.AccountSelectActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|locale|layoutDirection"
android:exported="true">
<intent-filter android:exported="true">
<action android:name="android.intent.action.MAIN" />
Expand Down Expand Up @@ -231,6 +247,15 @@
android:foregroundServiceType="dataSync"
android:exported="false" />

<service
android:name=".services.CSCarAppService"
android:exported="true">
<intent-filter>
<action android:name="androidx.car.app.CarAppService" />
<category android:name="androidx.car.app.category.NAVIGATION"/>
</intent-filter>
</service>

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
Expand Down
28 changes: 27 additions & 1 deletion app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ import coil3.SingletonImageLoader
import com.lagradost.api.setContext
import com.lagradost.cloudstream3.mvvm.safe
import com.lagradost.cloudstream3.mvvm.safeAsync
import com.lagradost.cloudstream3.network.initClient
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppContextUtils.openBrowser
import com.lagradost.cloudstream3.ui.car.CarStrings
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.getKeys
Expand Down Expand Up @@ -70,6 +73,8 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory {

override fun onCreate() {
super.onCreate()
app.initClient(this)
CarStrings.init(this)
// If we want to initialize Coil as early as possible, maybe when
// loading an image or GIF in a splash screen activity.
// buildImageLoader(applicationContext)
Expand All @@ -84,7 +89,7 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory {
}

override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
super.attachBaseContext(updateBaseContextLocale(base))
context = base
// This can be removed without deprecation after next stable
AcraApplication.context = context
Expand All @@ -98,6 +103,27 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory {
companion object {
var exceptionHandler: ExceptionHandler? = null

fun updateBaseContextLocale(context: Context?): Context? {
if (context == null) return null
val settingsManager = androidx.preference.PreferenceManager.getDefaultSharedPreferences(context)
val localeCode = settingsManager.getString(context.getString(R.string.locale_key), null)

// DEBUG LOGGING
println("DEBUG: updateBaseContextLocale called. localeCode from prefs: '$localeCode'")

if (localeCode.isNullOrEmpty()) return context

// Use forLanguageTag to correctly parse BCP 47 tags (e.g. "es", "es-ES", "it")
val locale = Locale.forLanguageTag(localeCode)
println("DEBUG: parsed Locale: '$locale', language: '${locale.language}', country: '${locale.country}'")

Locale.setDefault(locale)
val config = android.content.res.Configuration(context.resources.configuration)
config.setLocale(locale)
config.setLayoutDirection(locale)
return context.createConfigurationContext(config)
}

/** Use to get Activity from Context. */
tailrec fun Context.getActivity(): Activity? {
return when (this) {
Expand Down
8 changes: 6 additions & 2 deletions app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.safe
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.network.initClient

import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.PluginManager.___DO_NOT_CALL_FROM_A_PLUGIN_loadAllOnlinePlugins
import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin
Expand Down Expand Up @@ -197,6 +197,10 @@ import android.content.ComponentName
import android.content.ContentUris

class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(CloudStreamApp.updateBaseContextLocale(base))
}

companion object {
var activityResultLauncher: ActivityResultLauncher<Intent>? = null

Expand Down Expand Up @@ -1161,7 +1165,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa

@Suppress("DEPRECATION_ERROR")
override fun onCreate(savedInstanceState: Bundle?) {
app.initClient(this)

val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)

val errorFile = filesDir.resolve("last_error")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.lagradost.cloudstream3.services

import androidx.car.app.CarAppService
import androidx.car.app.Session
import androidx.car.app.validation.HostValidator
import com.lagradost.cloudstream3.ui.car.CarSession

class CSCarAppService : CarAppService() {
override fun createHostValidator(): HostValidator {
return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
}

override fun onCreateSession(): Session {
return CarSession()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,15 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding
import com.lagradost.cloudstream3.utils.UIHelper.openActivity
import com.lagradost.cloudstream3.utils.UIHelper.setNavigationBarColorCompat

import android.content.Context
import com.lagradost.cloudstream3.CloudStreamApp

class AccountSelectActivity : FragmentActivity(), BiometricCallback {

override fun attachBaseContext(base: Context?) {
super.attachBaseContext(CloudStreamApp.updateBaseContextLocale(base))
}

val accountViewModel: AccountViewModel by viewModels()

@SuppressLint("NotifyDataSetChanged")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.lagradost.cloudstream3.ui.car

import android.net.Uri
import androidx.car.app.AppManager
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.SurfaceCallback
import androidx.car.app.SurfaceContainer
import androidx.car.app.model.Action
import androidx.car.app.model.ActionStrip
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Template
import androidx.car.app.navigation.model.NavigationTemplate
import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import com.lagradost.cloudstream3.R

class AboutMeScreen(carContext: CarContext) : Screen(carContext), DefaultLifecycleObserver, SurfaceCallback {

private var player: ExoPlayer? = null

init {
lifecycle.addObserver(this)
}

override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
try {
player = ExoPlayer.Builder(carContext).build().apply {
val audioAttributes = androidx.media3.common.AudioAttributes.Builder()
.setUsage(androidx.media3.common.C.USAGE_MEDIA)
.setContentType(androidx.media3.common.C.AUDIO_CONTENT_TYPE_MOVIE)
.build()
setAudioAttributes(audioAttributes, true)

repeatMode = Player.REPEAT_MODE_ONE
volume = 1.0f
// android.resource://package/id
val uri = Uri.parse("android.resource://${carContext.packageName}/${R.raw.aboutme}")
setMediaItem(MediaItem.fromUri(uri))
prepare()
playWhenReady = true
}
} catch (e: Exception) {
e.printStackTrace()
}
}

override fun onDestroy(owner: LifecycleOwner) {
player?.release()
player = null
super.onDestroy(owner)
}

override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
carContext.getCarService(AppManager::class.java).setSurfaceCallback(this)
player?.play()
}

override fun onStop(owner: LifecycleOwner) {
player?.pause()
carContext.getCarService(AppManager::class.java).setSurfaceCallback(null)
super.onStop(owner)
}

override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) {
val surface = surfaceContainer.surface
if (surface != null) {
player?.setVideoSurface(surface)
}
}



override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) {
player?.clearVideoSurface()
}

override fun onGetTemplate(): Template {
val backAction = Action.Builder()
.setIcon(CarIcon.Builder(IconCompat.createWithResource(carContext, androidx.appcompat.R.drawable.abc_ic_ab_back_material)).build())
.setOnClickListener { screenManager.pop() }
.build()

return NavigationTemplate.Builder()
.setActionStrip(
ActionStrip.Builder()
.addAction(backAction)
.build()
)
.build()
}
}
Loading
Loading