Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1990551
android auto RC1
j4ckp0t85 Jan 6, 2026
7a1d015
prova risoluzione problema navigazione da android auto mentre l'auto …
j4ckp0t85 Jan 10, 2026
7645d6e
ANDROID AUTO RC2
j4ckp0t85 Jan 11, 2026
976679b
nuovo approccio per caricamento bookmarks e history (rimaneva scritta…
j4ckp0t85 Jan 11, 2026
14659d7
android auto RC3
j4ckp0t85 Jan 13, 2026
65814ba
-fix messaggio errore stilizzazione icone
j4ckp0t85 Jan 14, 2026
b42a369
ANDROID AUTO RC4
j4ckp0t85 Jan 14, 2026
f337a6b
ANDROID AUTO RC5
j4ckp0t85 Jan 15, 2026
b946fca
fix regressione salvataggio cronologia
j4ckp0t85 Jan 15, 2026
edd2d1d
ANDROID AUTO RC6
j4ckp0t85 Jan 16, 2026
95e8f12
Hello, World :)
j4ckp0t85 Jan 16, 2026
8fbd9fb
ANDROID AUTO RC6
j4ckp0t85 Jan 16, 2026
d375572
Merge remote-tracking branch 'origin/HEAD' into android-auto
j4ckp0t85 Jan 19, 2026
53acab7
bump versione app
j4ckp0t85 Jan 19, 2026
bf33062
cambio logica inizializzazione client (errori certificati su alcuni p…
j4ckp0t85 Jan 19, 2026
3d32de1
aggiornato README con disclaimer
j4ckp0t85 Jan 19, 2026
12abcc8
Merge commit 'c25a9dc56b22050ea9ed6b68f62de517ec82d3a3' into android-…
j4ckp0t85 Feb 2, 2026
c4a91b5
.
j4ckp0t85 Feb 2, 2026
ccdd686
(!! WIP Alpha !!)
j4ckp0t85 Feb 4, 2026
bd17667
aggiunta locandina nel categoryscreen
j4ckp0t85 Feb 5, 2026
40c4456
bump versione
j4ckp0t85 Feb 5, 2026
4ce6ee0
refactor
j4ckp0t85 Feb 6, 2026
36a8bea
migliorie screen dettagli film e serie tv
j4ckp0t85 Feb 9, 2026
849c4cc
ricerca real-time con debounce time di 400 millisecondi
j4ckp0t85 Feb 9, 2026
e05a0cb
fix - (serie tv) reset seek time al caricamento dell'episodio succes…
j4ckp0t85 Feb 9, 2026
d522e6e
Merge remote-tracking branch 'origin/master' into android-auto
j4ckp0t85 Feb 9, 2026
0fdb04e
bump versione
j4ckp0t85 Feb 9, 2026
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.20"

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