diff --git a/README.md b/README.md index c2492c5d821..05dc88586d3 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 41e8fc0a01a..06820bcda9f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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() @@ -152,7 +163,15 @@ android { resValues = true } + + namespace = "com.lagradost.cloudstream3" + + sourceSets { + getByName("main") { + res.srcDirs("src/main/res", "src/main/res-car") + } + } } dependencies { @@ -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) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e1bc9ac978..5ab1ff2db93 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ - + @@ -22,6 +22,10 @@ android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" /> + + + + + + + + + + + + @@ -111,7 +127,7 @@ --> @@ -231,6 +247,15 @@ android:foregroundServiceType="dataSync" android:exported="false" /> + + + + + + + ? = null @@ -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") diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/CSCarAppService.kt b/app/src/main/java/com/lagradost/cloudstream3/services/CSCarAppService.kt new file mode 100644 index 00000000000..bc10bc4133e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/services/CSCarAppService.kt @@ -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() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt index 42f68067b8c..dafd8141041 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt @@ -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") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/AboutMeScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/AboutMeScreen.kt new file mode 100644 index 00000000000..7e4c98a2780 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/AboutMeScreen.kt @@ -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() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/BookmarksScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/BookmarksScreen.kt new file mode 100644 index 00000000000..6f70f191384 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/BookmarksScreen.kt @@ -0,0 +1,104 @@ +package com.lagradost.cloudstream3.ui.car + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.CarIcon +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import androidx.core.graphics.drawable.IconCompat +import com.lagradost.cloudstream3.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import com.lagradost.cloudstream3.TvType +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.coroutines.delay +import coil3.SingletonImageLoader +import coil3.request.ImageRequest +import coil3.asDrawable + +class BookmarksScreen(carContext: CarContext) : Screen(carContext), androidx.lifecycle.DefaultLifecycleObserver { + private var itemList: ItemList? = null + + init { + lifecycle.addObserver(this) + } + + override fun onStart(owner: androidx.lifecycle.LifecycleOwner) { + loadBookmarks() + } + + private fun loadBookmarks(retryCount: Int = 0) { + CoroutineScope(Dispatchers.IO).launch { + withContext(Dispatchers.Main) { + itemList = null + invalidate() + } + try { + // Fetch only Favorites (Heart icon) + val favorites = com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites() + .sortedByDescending { it.favoritesTime } + + val builder = ItemList.Builder() + if (favorites.isEmpty()) { + builder.setNoItemsMessage(CarStrings.get(R.string.car_no_favorites_found)) + } else { + favorites.forEach { item -> + builder.addItem( + Row.Builder() + .setTitle(item.name) + .setOnClickListener { + val type = item.type + if (type == TvType.TvSeries || + type == TvType.Anime || + type == TvType.Cartoon || + type == TvType.OVA || + type == TvType.AsianDrama || + type == TvType.Documentary) { + screenManager.push(TvSeriesDetailScreen(carContext, item)) + } else { + screenManager.push(DetailsScreen(carContext, item)) + } + } + .build() + ) + } + } + + val builtList = builder.build() + withContext(Dispatchers.Main) { + itemList = builtList + invalidate() + } + } catch (e: Exception) { + if (retryCount < 3) { + delay(3000) + loadBookmarks(retryCount + 1) + } else { + withContext(Dispatchers.Main) { + itemList = ItemList.Builder() + .addItem( + Row.Builder() + .setTitle("${CarStrings.get(R.string.car_error)}: ${e.message}") + .setOnClickListener { loadBookmarks() } + .build() + ) + .build() + invalidate() + } + } + } + } + } + + override fun onGetTemplate(): Template { + return ListTemplate.Builder() + .setSingleList(itemList ?: ItemList.Builder().setNoItemsMessage(CarStrings.get(R.string.car_loading)).build()) + .setTitle(CarStrings.get(R.string.car_favorites)) + .setHeaderAction(Action.BACK) + .build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/CarPlayerPresentation.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/CarPlayerPresentation.kt new file mode 100644 index 00000000000..dd7294d46ae --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/CarPlayerPresentation.kt @@ -0,0 +1,247 @@ +package com.lagradost.cloudstream3.ui.car + +import android.app.Presentation +import android.content.Context +import android.graphics.Matrix +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import android.util.Log +import android.view.Display +import android.view.MotionEvent +import android.view.TextureView +import android.view.View +import android.view.WindowManager +import android.widget.ImageButton +import android.widget.ProgressBar +import android.widget.TextView +import androidx.media3.exoplayer.ExoPlayer +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.PlayerCarHelper + +/** + * Presentation projected onto the car's VirtualDisplay. + * + * Uses TextureView (not SurfaceView) because VirtualDisplay cannot composite + * SurfaceView's separate layer back to its output Surface. + * FLAG_HARDWARE_ACCELERATED is required for TextureView. + */ +class CarPlayerPresentation( + context: Context, + display: Display, + private val getPlayer: () -> ExoPlayer?, + private val getIsPlaying: () -> Boolean, + private val onSeekBack: () -> Unit, + private val onSeekForward: () -> Unit, + private val onSaveProgress: () -> Unit, + private val getTitle: () -> String, + private val getSubtitle: () -> String? +) : Presentation(context, display) { + + companion object { + private const val TAG = "CarPlayerPresentation" + private const val HIDE_DELAY_MS = 5000L + } + + private var btnPlay: ImageButton? = null + private var btnPause: ImageButton? = null + private var btnRewind: ImageButton? = null + private var btnForward: ImageButton? = null + private var txtTitle: TextView? = null + private var txtSubtitle: TextView? = null + private var txtCurrentTime: TextView? = null + private var txtDuration: TextView? = null + private var progressBar: ProgressBar? = null + private var controlsContainer: View? = null + private var textureView: TextureView? = null + + private val hideHandler = Handler(Looper.getMainLooper()) + private val hideRunnable = Runnable { toggleControls(false) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // Required for TextureView inside VirtualDisplay + window?.setFlags( + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + ) + + setContentView(R.layout.remote_car_player_overlay) + bindViews() + setupListeners() + updateMetadata() + updatePlayPauseState() + scheduleHideControls() + } + + private fun bindViews() { + textureView = findViewById(R.id.texture_view) + controlsContainer = findViewById(R.id.controls_container) + btnPlay = findViewById(R.id.btn_play) + btnPause = findViewById(R.id.btn_pause) + btnRewind = findViewById(R.id.btn_rewind) + btnForward = findViewById(R.id.btn_forward) + txtTitle = findViewById(R.id.player_title) + txtSubtitle = findViewById(R.id.player_subtitle) + txtCurrentTime = findViewById(R.id.text_current_time) + txtDuration = findViewById(R.id.text_duration) + progressBar = findViewById(R.id.progress_bar) + } + + private fun setupListeners() { + textureView?.let { tv -> + Log.d(TAG, "Binding TextureView to player") + getPlayer()?.setVideoTextureView(tv) + } + + btnPlay?.setOnClickListener { + getPlayer()?.play() + scheduleHideControls() + } + btnPause?.setOnClickListener { + getPlayer()?.pause() + onSaveProgress() + scheduleHideControls() + } + btnRewind?.setOnClickListener { + onSeekBack() + scheduleHideControls() + } + btnForward?.setOnClickListener { + onSeekForward() + scheduleHideControls() + } + controlsContainer?.setOnClickListener { + if (controlsContainer?.visibility == View.VISIBLE) { + scheduleHideControls() + } + } + } + + // --- Public API --- + + fun getTextureView(): TextureView? = textureView + + fun dispatchTouch(x: Float, y: Float) { + val now = SystemClock.uptimeMillis() + val down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, x, y, 0) + val up = MotionEvent.obtain(now, now + 50, MotionEvent.ACTION_UP, x, y, 0) + + try { + val pb = progressBar + if (pb != null && controlsContainer?.visibility == View.VISIBLE) { + val loc = IntArray(2) + pb.getLocationOnScreen(loc) + val pbX = loc[0] + val pbY = loc[1] + + if (x >= pbX && x <= (pbX + pb.width) && + y >= (pbY - 20) && y <= (pbY + pb.height + 40) + ) { + val ratio = (x - pbX) / pb.width.toFloat() + val dur = getPlayer()?.duration ?: 0L + if (dur > 0) { + getPlayer()?.seekTo((dur * ratio).toLong()) + scheduleHideControls() + return + } + } + } + window?.superDispatchTouchEvent(down) + window?.superDispatchTouchEvent(up) + } catch (e: Exception) { + Log.e(TAG, "Touch dispatch failed", e) + } finally { + down.recycle() + up.recycle() + } + + if (controlsContainer?.visibility != View.VISIBLE) { + toggleControls(true) + } else { + scheduleHideControls() + } + } + + fun toggleControls(show: Boolean) { + hideHandler.removeCallbacks(hideRunnable) + if (show) { + controlsContainer?.visibility = View.VISIBLE + controlsContainer?.animate()?.alpha(1.0f)?.setDuration(200)?.start() + scheduleHideControls() + } else { + controlsContainer?.animate()?.alpha(0.0f)?.setDuration(500)?.withEndAction { + controlsContainer?.visibility = View.GONE + }?.start() + } + } + + fun scheduleHideControls() { + hideHandler.removeCallbacks(hideRunnable) + hideHandler.postDelayed(hideRunnable, HIDE_DELAY_MS) + } + + fun updatePlayPauseState() { + if (getIsPlaying()) { + btnPlay?.visibility = View.GONE + btnPause?.visibility = View.VISIBLE + } else { + btnPlay?.visibility = View.VISIBLE + btnPause?.visibility = View.GONE + toggleControls(true) + } + } + + fun updateProgress(current: Long, total: Long) { + txtCurrentTime?.text = PlayerCarHelper.formatDuration(current) + txtDuration?.text = PlayerCarHelper.formatDuration(total) + if (total > 0) { + progressBar?.progress = ((current * 1000) / total).toInt() + } + } + + fun updateMetadata() { + txtTitle?.text = getTitle() + val subtitle = getSubtitle() + if (subtitle != null) { + txtSubtitle?.text = subtitle + txtSubtitle?.visibility = View.VISIBLE + } else { + txtSubtitle?.visibility = View.GONE + } + } + + /** + * Apply fit/fill scaling via Matrix transform. + * TextureView doesn't support MediaCodec's videoScalingMode. + */ + fun applyVideoScale(fill: Boolean) { + val tv = textureView ?: return + val videoWidth = getPlayer()?.videoSize?.width ?: return + val videoHeight = getPlayer()?.videoSize?.height ?: return + if (videoWidth == 0 || videoHeight == 0) return + + val viewWidth = tv.width.toFloat() + val viewHeight = tv.height.toFloat() + if (viewWidth == 0f || viewHeight == 0f) return + + val matrix = Matrix() + if (fill) { + val videoAspect = videoWidth.toFloat() / videoHeight.toFloat() + val viewAspect = viewWidth / viewHeight + val scaleX: Float + val scaleY: Float + if (videoAspect > viewAspect) { + scaleY = 1f + scaleX = videoAspect / viewAspect + } else { + scaleX = 1f + scaleY = viewAspect / videoAspect + } + matrix.setScale(scaleX, scaleY, viewWidth / 2f, viewHeight / 2f) + } + tv.setTransform(matrix) + Log.d(TAG, "Video scale: fill=$fill, video=${videoWidth}x$videoHeight, view=${viewWidth.toInt()}x${viewHeight.toInt()}") + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/CarSession.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/CarSession.kt new file mode 100644 index 00000000000..0207176830b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/CarSession.kt @@ -0,0 +1,31 @@ +package com.lagradost.cloudstream3.ui.car + +import android.content.Intent +import android.util.Log +import androidx.car.app.Screen +import androidx.car.app.Session +import androidx.lifecycle.coroutineScope +import com.lagradost.cloudstream3.plugins.PluginManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class CarSession : Session() { + companion object { + private const val TAG = "CarSession" + } + + init { + lifecycle.coroutineScope.launch(Dispatchers.IO) { + try { + @Suppress("DEPRECATION_ERROR") + PluginManager.___DO_NOT_CALL_FROM_A_PLUGIN_loadAllOnlinePlugins(carContext) + } catch (e: Exception) { + Log.e(TAG, "Error loading plugins", e) + } + } + } + + override fun onCreateScreen(intent: Intent): Screen { + return MainCarScreen(carContext) + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/CarStrings.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/CarStrings.kt new file mode 100644 index 00000000000..3d27aab57f4 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/CarStrings.kt @@ -0,0 +1,51 @@ +package com.lagradost.cloudstream3.ui.car + +import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources +import androidx.annotation.StringRes +import androidx.preference.PreferenceManager +import com.lagradost.cloudstream3.R +import java.util.Locale + +/** + * Singleton helper for Android Auto localization. + * Wraps Android Resources to support overriding the language based on app settings, + * independent of the system locale. + */ +object CarStrings { + private var resources: Resources? = null + + /** + * Initialize the helper with the application context. + * Call this in Application.onCreate(). + */ + fun init(context: Context) { + // Read user preference + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val localeCode = prefs.getString(context.getString(R.string.locale_key), null) + + // If a specific locale is set (e.g. "it"), force it. + // Otherwise, use system default. + if (!localeCode.isNullOrEmpty()) { + val locale = Locale(localeCode) + val config = Configuration(context.resources.configuration) + config.setLocale(locale) + resources = context.createConfigurationContext(config).resources + } else { + resources = context.resources + } + } + + /** + * Get a localized string by Resource ID. + */ + fun get(@StringRes id: Int, vararg args: Any): String { + val res = resources ?: throw IllegalStateException("CarStrings not initialized! Call init() in Application.onCreate()") + return if (args.isEmpty()) { + res.getString(id) + } else { + res.getString(id, *args) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/CategoryScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/CategoryScreen.kt new file mode 100644 index 00000000000..2568ba1d75f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/CategoryScreen.kt @@ -0,0 +1,159 @@ +package com.lagradost.cloudstream3.ui.car + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.ActionStrip +import androidx.car.app.model.CarIcon +import androidx.car.app.model.GridItem +import androidx.car.app.model.GridTemplate +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.OnClickListener +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import androidx.core.graphics.drawable.IconCompat +import androidx.core.graphics.drawable.toBitmap +import coil3.asDrawable +import coil3.request.ImageRequest +import coil3.size.Scale +import coil3.size.Precision +import coil3.SingletonImageLoader +import com.lagradost.cloudstream3.HomePageList +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.DataStoreHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +class CategoryScreen( + carContext: CarContext, + private val homePageList: HomePageList +) : Screen(carContext) { + + private val scope = CoroutineScope(Dispatchers.IO + Job()) + private val iconCache = mutableMapOf() + private val loadingUrls = mutableSetOf() + + private fun loadIcon(imageUrl: String) { + if (loadingUrls.contains(imageUrl)) return + loadingUrls.add(imageUrl) + + scope.launch { + try { + val request = ImageRequest.Builder(carContext) + .data(imageUrl) + .size(256, 256) + .build() + val result = SingletonImageLoader.get(carContext).execute(request) + val bitmap = result.image?.asDrawable(carContext.resources)?.toBitmap() + if (bitmap != null) { + val icon = CarIcon.Builder(IconCompat.createWithBitmap(bitmap)).build() + synchronized(iconCache) { + iconCache[imageUrl] = icon + } + invalidate() + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + loadingUrls.remove(imageUrl) + } + } + } + + override fun onGetTemplate(): Template { + val currentViewMode = DataStoreHelper.carCategoryViewMode // 0 = List, 1 = Grid + + val actionStrip = buildActionStrip(currentViewMode) + + val listBuilder = ItemList.Builder() + + homePageList.list.forEach { item -> + val imageUrl = item.posterUrl + + // Check cache or trigger load + val icon = if (!imageUrl.isNullOrEmpty()) { + synchronized(iconCache) { + iconCache[imageUrl] + } ?: run { + loadIcon(imageUrl) + CarIcon.Builder(IconCompat.createWithResource(carContext, R.mipmap.ic_launcher)).build() + } + } else { + CarIcon.Builder(IconCompat.createWithResource(carContext, R.mipmap.ic_launcher)).build() + } + + val onClick = OnClickListener { + val type = item.type + if (type == com.lagradost.cloudstream3.TvType.TvSeries || + type == com.lagradost.cloudstream3.TvType.Anime || + type == com.lagradost.cloudstream3.TvType.Cartoon || + type == com.lagradost.cloudstream3.TvType.OVA || + type == com.lagradost.cloudstream3.TvType.AsianDrama || + type == com.lagradost.cloudstream3.TvType.Documentary) { + screenManager.push(TvSeriesDetailScreen(carContext, item)) + } else if (type == com.lagradost.cloudstream3.TvType.Live) { + screenManager.push(PlayerCarScreen(carContext, item = item)) + } else { + screenManager.push(DetailsScreen(carContext, item)) + } + } + + if (currentViewMode == 0) { + // Use Row with LARGE image for maximum visibility in a list + val row = Row.Builder() + .setTitle(if (item.name.isNullOrEmpty()) "Untitled" else item.name) + .setImage(icon, Row.IMAGE_TYPE_LARGE) + .setOnClickListener(onClick) + .build() + listBuilder.addItem(row) + } else { + val gridItem = GridItem.Builder() + .setTitle(if (item.name.isNullOrEmpty()) "Untitled" else item.name) + .setImage(icon, GridItem.IMAGE_TYPE_LARGE) + .setOnClickListener(onClick) + .build() + listBuilder.addItem(gridItem) + } + } + + return if (currentViewMode == 0) { + ListTemplate.Builder() + .setTitle(homePageList.name) + .setHeaderAction(Action.BACK) + .setSingleList(listBuilder.build()) + .setActionStrip(actionStrip) + .build() + } else { + GridTemplate.Builder() + .setTitle(homePageList.name) + .setHeaderAction(Action.BACK) + .setSingleList(listBuilder.build()) + .setActionStrip(actionStrip) + .build() + } + } + + private fun buildActionStrip(currentViewMode: Int): ActionStrip { + return ActionStrip.Builder() + .addAction( + Action.Builder() + .setIcon( + CarIcon.Builder( + IconCompat.createWithResource( + carContext, + if (currentViewMode == 0) R.drawable.baseline_grid_view_24 else R.drawable.baseline_list_alt_24 + ) + ).build() + ) + .setOnClickListener { + DataStoreHelper.carCategoryViewMode = if (currentViewMode == 0) 1 else 0 + invalidate() + } + .build() + ) + .build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/DetailsScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/DetailsScreen.kt new file mode 100644 index 00000000000..6cf3d95bf0b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/DetailsScreen.kt @@ -0,0 +1,320 @@ +package com.lagradost.cloudstream3.ui.car + +import com.lagradost.cloudstream3.utils.CarHelper + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.CarColor +import androidx.car.app.model.Pane +import androidx.car.app.model.PaneTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.TvSeriesLoadResponse +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.newTvSeriesSearchResponse +import com.lagradost.cloudstream3.ui.player.DownloadedPlayerActivity +import android.content.Intent +import android.net.Uri +import android.util.Log +import android.text.SpannableString +import android.text.Spanned +import androidx.car.app.model.CarIcon +import androidx.car.app.model.ForegroundCarColorSpan +import androidx.core.graphics.drawable.IconCompat +import androidx.core.graphics.drawable.toBitmap +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import coil3.asDrawable +import coil3.request.ImageRequest +import coil3.SingletonImageLoader +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.ui.result.ResultViewModel2 +import com.lagradost.cloudstream3.utils.DataStoreHelper.FavoritesData +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.MovieLoadResponse +import com.lagradost.cloudstream3.ui.result.getId +import com.lagradost.cloudstream3.utils.DataStoreHelper.getVideoWatchState +import com.lagradost.cloudstream3.ui.result.VideoWatchState + +class DetailsScreen( + carContext: CarContext, + private val item: SearchResponse +) : Screen(carContext) { + + private var fullDetails: LoadResponse? = null + private var isLoading = true + private var errorMessage: String? = null + + private var posterBitmap: android.graphics.Bitmap? = null + private var logoBitmap: android.graphics.Bitmap? = null + private var isFavorite: Boolean = false + private val scope = CoroutineScope(Dispatchers.IO + Job()) + + // Selected source for playback + private var selectedSource: ExtractorLink? = null + + init { + loadData() + } + + private fun loadData() { + scope.launch { + try { + + + // Load Details + val api = getApiFromNameNull(item.apiName) + if (api != null) { + val repo = APIRepository(api) + when (val result = repo.load(item.url)) { + is Resource.Success -> { + fullDetails = result.value + + // Redirect if Type Mismatch (e.g. Provider reported Movie, but it's a Series) + // We check if the response is structurally a Series (has episodes etc) + // This fixes providers that return "Movie" type but provide series data. + if (result.value is TvSeriesLoadResponse) { + val detectedType = result.value.type + // If the provider says "Movie" but sends TvSeriesLoadResponse, force TvSeries type + val isTv = detectedType == TvType.TvSeries || + detectedType == TvType.Anime || + detectedType == TvType.Cartoon || + detectedType == TvType.OVA || + detectedType == TvType.AsianDrama || + detectedType == TvType.Documentary + + val finalType = if (isTv) detectedType else TvType.TvSeries + + val correctItem = api.newTvSeriesSearchResponse( + name = result.value.name, + url = result.value.url, + type = finalType, + ) { + this.posterUrl = result.value.posterUrl ?: item.posterUrl + } + withContext(Dispatchers.Main) { + screenManager.pop() + screenManager.push(TvSeriesDetailScreen(carContext, correctItem)) + } + return@launch + } + + // Check ID logic matches standard + val id = result.value.url.replace(api.mainUrl, "").replace("/", "").hashCode() + isFavorite = DataStoreHelper.getFavoritesData(id) != null + + // Load Main Image (Background/Landscape preferred, else Poster) + val bgUrl = result.value.backgroundPosterUrl + val posterUrl = result.value.posterUrl ?: item.posterUrl + val targetUrl = bgUrl ?: posterUrl + + if (!targetUrl.isNullOrEmpty()) { + try { + val request = ImageRequest.Builder(carContext) + .data(targetUrl) + .size(1200, 800) // Landscape optimized size if possible, or high res + .build() + val imgResult = SingletonImageLoader.get(carContext).execute(request) + posterBitmap = imgResult.image?.asDrawable(carContext.resources)?.toBitmap()?.let { + CarHelper.ensureSoftwareBitmap(it) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + if (!result.value.logoUrl.isNullOrEmpty()) { + try { + val request = ImageRequest.Builder(carContext) + .data(result.value.logoUrl) + .size(600, 200) + .build() + val imgResult = SingletonImageLoader.get(carContext).execute(request) + logoBitmap = imgResult.image?.asDrawable(carContext.resources)?.toBitmap()?.let { + CarHelper.ensureSoftwareBitmap(it) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + isLoading = false + } + is Resource.Failure -> { + errorMessage = result.errorString + isLoading = false + } + is Resource.Loading -> {} + } + } else { + errorMessage = CarStrings.get(R.string.car_provider_not_found) + isLoading = false + } + } catch (e: Exception) { + errorMessage = e.message + isLoading = false + } + invalidate() + } + } + + override fun onGetTemplate(): Template { + val paneBuilder = Pane.Builder() + + if (isLoading) { + paneBuilder.setLoading(true) + } else { + buildContent(paneBuilder) + } + + buildActions(paneBuilder) + + val title = fullDetails?.let { data -> + val id = data.getId() + val watchState = getVideoWatchState(id) + val isWatched = watchState == VideoWatchState.Watched + if (isWatched) "✅ ${data.name}" else data.name + } ?: item.name + + return PaneTemplate.Builder(paneBuilder.build()) + .setTitle(title) + .setHeaderAction(Action.BACK) + .build() + } + + private fun buildContent(paneBuilder: Pane.Builder) { + val details = fullDetails + + // Set Hero Image on the Pane itself for maximum size + // Set Hero Image on the Pane itself for maximum size + // Overlay Strategy: Gradient at bottom + Logo Bottom-Left + // Force Square construction even if logo is null, to ensure we control the aspect ratio + val finalBitmap = if (posterBitmap != null) { + CarHelper.generateSquareImageWithLogo(posterBitmap!!, logoBitmap) + } else { + posterBitmap + } + + finalBitmap?.let { + paneBuilder.setImage(CarIcon.Builder(IconCompat.createWithBitmap(it)).build()) + } ?: run { + paneBuilder.setImage(CarIcon.Builder(IconCompat.createWithResource(carContext, R.mipmap.ic_launcher)).build()) + } + + if (details != null) { + // Meta Row: Year • Rating • Duration + val metaStringBuilder = StringBuilder() + details.year?.let { metaStringBuilder.append("$it") } + + // Add Score if available + val score = details.score + if (score != null) { + if (metaStringBuilder.isNotEmpty()) metaStringBuilder.append(" • ") + metaStringBuilder.append(String.format("%.1f/10", score.toDouble(10))) + } + + details.duration?.let { + if (metaStringBuilder.isNotEmpty()) metaStringBuilder.append(" • ") + metaStringBuilder.append("${it}m") + } + + if (metaStringBuilder.isNotEmpty()) { + paneBuilder.addRow( + Row.Builder() + .setTitle(metaStringBuilder.toString()) + .build() + ) + } + + // Check watch state + val id = details.getId() + val watchState = getVideoWatchState(id) + val isWatched = watchState == VideoWatchState.Watched + + val title = if (isWatched) "✅ ${details.name}" else details.name + + paneBuilder.addRow( + Row.Builder() + .setTitle(title) + .addText(details.plot ?: "") + .build() + ) + // Plot and Cast + CarHelper.addPlotAndCast(paneBuilder, details) + } else if (errorMessage != null) { + paneBuilder.addRow(Row.Builder().setTitle("${CarStrings.get(R.string.car_error)}: $errorMessage").build()) + } + } + + private fun buildActions(paneBuilder: Pane.Builder) { + // Play Button: White background with Black Icon + val playIcon = IconCompat.createWithResource(carContext, android.R.drawable.ic_media_play) + .setTint(android.graphics.Color.BLACK) + + val playAction = Action.Builder() + .setIcon(CarIcon.Builder(playIcon).build()) + .setBackgroundColor(CarColor.createCustom(android.graphics.Color.WHITE, android.graphics.Color.WHITE)) + .setOnClickListener { + screenManager.push(PlayerCarScreen( + carContext = carContext, + item = item, + preSelectedSource = selectedSource + )) + } + .build() + + // Source Selection Button + val sourceIcon = IconCompat.createWithResource(carContext, R.drawable.ic_baseline_source_24) + + val sourceActionTitle = if (selectedSource != null) { + selectedSource!!.name + } else { + CarStrings.get(R.string.car_source) + } + + val sourceAction = Action.Builder() + .setIcon(CarIcon.Builder(sourceIcon).build()) + .setTitle(sourceActionTitle) + .setOnClickListener { + val details = fullDetails + val dataUrl = when (details) { + is MovieLoadResponse -> details.dataUrl + else -> details?.url + } + if (dataUrl != null && details != null) { + screenManager.push( + SourceSelectionScreen( + carContext = carContext, + apiName = details.apiName, + dataUrl = dataUrl, + currentSourceUrl = selectedSource?.url, + onSourceSelected = { source -> + selectedSource = source + invalidate() + } + ) + ) + } + } + .build() + + paneBuilder.addAction(playAction) + paneBuilder.addAction(sourceAction) + } + + private fun toggleFavorite() { + CarHelper.toggleFavorite(carContext, fullDetails, isFavorite) { newStatus -> + isFavorite = newStatus + invalidate() + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/DownloadsScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/DownloadsScreen.kt new file mode 100644 index 00000000000..424d3bbb116 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/DownloadsScreen.kt @@ -0,0 +1,213 @@ +package com.lagradost.cloudstream3.ui.car + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import com.lagradost.cloudstream3.utils.VideoDownloadManager +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.getKeys +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE +import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE +import com.lagradost.cloudstream3.utils.DataStore.getFolderName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import com.lagradost.cloudstream3.isEpisodeBased + +class DownloadsScreen( + carContext: CarContext, + private val parentId: Int? = null, + private val headerName: String? = null +) : Screen(carContext) { + private val scope = CoroutineScope(Dispatchers.IO) + private var itemList: ItemList? = null + + init { + loadContent() + } + + private fun loadContent() { + if (parentId != null) { + loadEpisodes(parentId) + } else { + loadHeaders() + } + } + + private fun loadEpisodes(id: Int) { + scope.launch { + val context = carContext + val children = context.getKeys(DOWNLOAD_EPISODE_CACHE) + .mapNotNull { context.getKey(it) } + .filter { it.parentId == id } + .filter { + val info = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, it.id) + (info?.fileLength ?: 0L) > 0 + } + .sortedWith(compareBy({ it.season }, { it.episode })) + + val builder = ItemList.Builder() + + if (children.isEmpty()) { + builder.setNoItemsMessage(CarStrings.get(R.string.car_no_episodes_found)) + } else { + children.forEach { episode -> + val name = "S${episode.season}:E${episode.episode} - ${episode.name ?: CarStrings.get(R.string.car_episode)}" + builder.addItem( + Row.Builder() + .setTitle(name) + .setOnClickListener { + playEpisode(episode.id, id) + } + .build() + ) + } + } + + val builtList = builder.build() + withContext(Dispatchers.Main) { + itemList = builtList + invalidate() + } + } + } + + private fun loadHeaders() { + scope.launch { + val headers = carContext.getKeys(DOWNLOAD_HEADER_CACHE) + .mapNotNull { carContext.getKey(it) } + .sortedBy { it.name } + + val builder = ItemList.Builder() + + // Filter and find valid items + val validHeaders = headers.filter { header -> + val context = carContext + val id = header.id + + // Get all children (episodes or movies) for this header + val children = context.getKeys(DOWNLOAD_EPISODE_CACHE) + .mapNotNull { context.getKey(it) } + .filter { it.parentId == id } + + // Check if AT LEAST ONE child is valid (> 0 bytes) + children.any { child -> + val info = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, child.id) + (info?.fileLength ?: 0L) > 0 + } + } + + if (validHeaders.isEmpty()) { + builder.setNoItemsMessage(CarStrings.get(R.string.car_no_downloads_found)) + } else { + validHeaders.forEach { header -> + val lastWatched = DataStoreHelper.getLastWatched(header.id) + val subtitle = if (lastWatched != null && lastWatched.season != null && lastWatched.episode != null) { + "S${lastWatched.season}E${lastWatched.episode}" + } else { + null + } + + val rowBuilder = Row.Builder() + .setTitle(header.name) + .setOnClickListener { + onHeaderClick(header) + } + + if (subtitle != null) { + rowBuilder.addText(subtitle) + } + + builder.addItem(rowBuilder.build()) + } + } + + val builtList = builder.build() + withContext(Dispatchers.Main) { + itemList = builtList + invalidate() + } + } + } + + private fun playEpisode(episodeId: Int, parentId: Int) { + scope.launch { + val context = carContext + val fileInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, episodeId) + if (fileInfo?.path == null) { + withContext(Dispatchers.Main) { + androidx.car.app.CarToast.makeText(carContext, CarStrings.get(R.string.car_file_not_found), androidx.car.app.CarToast.LENGTH_SHORT).show() + } + return@launch + } + + // Get saved resume position + val savedPos = DataStoreHelper.getViewPos(episodeId) + val startTime = savedPos?.position ?: 0L + + withContext(Dispatchers.Main) { + screenManager.push( + PlayerCarScreen( + carContext = carContext, + fileUri = fileInfo.path.toString(), + videoId = episodeId, + parentId = parentId, + startTime = startTime + ) + ) + } + } + } + + private fun onHeaderClick(header: VideoDownloadHelper.DownloadHeaderCached) { + scope.launch { + val context = carContext + val id = header.id + + // Get children + val children = context.getKeys(DOWNLOAD_EPISODE_CACHE) + .mapNotNull { context.getKey(it) } + .filter { it.parentId == id } + .filter { + val info = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, it.id) + (info?.fileLength ?: 0L) > 0 + } + + if (children.isEmpty()) { + withContext(Dispatchers.Main) { + androidx.car.app.CarToast.makeText(carContext, CarStrings.get(R.string.car_no_valid_episode), androidx.car.app.CarToast.LENGTH_SHORT).show() + } + return@launch + } + + if (header.type.isEpisodeBased()) { + // Series: Always go to episode list + withContext(Dispatchers.Main) { + screenManager.push(DownloadsScreen(carContext, id, header.name)) + } + } else { + // Movie: Just pick the first/only one + val episodeId = children.firstOrNull()?.id + if (episodeId != null) { + playEpisode(episodeId, id) + } + } + } + } + + override fun onGetTemplate(): Template { + return ListTemplate.Builder() + .setTitle(headerName ?: CarStrings.get(R.string.car_downloads)) + .setHeaderAction(Action.BACK) + .setSingleList(itemList ?: ItemList.Builder().setNoItemsMessage(CarStrings.get(R.string.car_loading)).build()) + .build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/EpisodeDetailScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/EpisodeDetailScreen.kt new file mode 100644 index 00000000000..84060f26909 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/EpisodeDetailScreen.kt @@ -0,0 +1,155 @@ +package com.lagradost.cloudstream3.ui.car + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.CarColor +import androidx.car.app.model.CarIcon +import androidx.car.app.model.Pane +import androidx.car.app.model.PaneTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import androidx.core.graphics.drawable.IconCompat +import androidx.core.graphics.drawable.toBitmap +import coil3.asDrawable +import coil3.request.ImageRequest +import coil3.SingletonImageLoader +import com.lagradost.cloudstream3.Episode +import com.lagradost.cloudstream3.TvSeriesLoadResponse +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.ExtractorLink +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +class EpisodeDetailScreen( + carContext: CarContext, + private val seriesDetails: TvSeriesLoadResponse, + private val episode: Episode, + private val playlist: List +) : Screen(carContext) { + + private val scope = CoroutineScope(Dispatchers.IO + Job()) + private var posterBitmap: android.graphics.Bitmap? = null + private var isLoadingImage = true + + // Selected source for playback + private var selectedSource: ExtractorLink? = null + + init { + loadImage() + } + + private fun loadImage() { + scope.launch { + // Try episode poster first, then series poster + val url = episode.posterUrl ?: seriesDetails.posterUrl + if (!url.isNullOrEmpty()) { + try { + val request = ImageRequest.Builder(carContext) + .data(url) + .size(600, 900) + .build() + val result = SingletonImageLoader.get(carContext).execute(request) + posterBitmap = result.image?.asDrawable(carContext.resources)?.toBitmap() + } catch (e: Exception) { + e.printStackTrace() + } + } + isLoadingImage = false + invalidate() + } + } + + override fun onGetTemplate(): Template { + val paneBuilder = Pane.Builder() + + // Title Row + val title = "${episode.episode}. ${episode.name ?: "${CarStrings.get(R.string.car_episode)} ${episode.episode}"}" + paneBuilder.addRow( + Row.Builder() + .setTitle(title) + .addText("${CarStrings.get(R.string.car_season)} ${episode.season ?: "?"}") + .build() + ) + + // Image + posterBitmap?.let { + paneBuilder.setImage(CarIcon.Builder(IconCompat.createWithBitmap(it)).build()) + } ?: run { + // Fallback icon if no image loaded yet or error + paneBuilder.setImage(CarIcon.Builder(IconCompat.createWithResource(carContext, R.mipmap.ic_launcher)).build()) + } + + // Description Row + if (!episode.description.isNullOrEmpty()) { + paneBuilder.addRow( + Row.Builder() + .setTitle(CarStrings.get(R.string.car_plot)) + .addText(episode.description!!) + .build() + ) + } + + // Play Button action + val playIcon = IconCompat.createWithResource(carContext, android.R.drawable.ic_media_play) + .setTint(android.graphics.Color.BLACK) + + val playAction = Action.Builder() + .setIcon(CarIcon.Builder(playIcon).build()) + .setBackgroundColor(CarColor.createCustom(android.graphics.Color.WHITE, android.graphics.Color.WHITE)) + .setOnClickListener { + // Get saved position for resume + val startTime = getViewPos(episode.data.hashCode())?.position ?: 0L + screenManager.push( + PlayerCarScreen( + carContext = carContext, + loadResponse = seriesDetails, + selectedEpisode = episode, + playlist = playlist, + startTime = startTime, + preSelectedSource = selectedSource + ) + ) + } + .build() + + // Source Selection Button + val sourceIcon = IconCompat.createWithResource(carContext, R.drawable.ic_baseline_source_24) + + val sourceActionTitle = if (selectedSource != null) { + selectedSource!!.name + } else { + CarStrings.get(R.string.car_source) + } + + val sourceAction = Action.Builder() + .setIcon(CarIcon.Builder(sourceIcon).build()) + .setTitle(sourceActionTitle) + .setOnClickListener { + screenManager.push( + SourceSelectionScreen( + carContext = carContext, + apiName = seriesDetails.apiName, + dataUrl = episode.data, + currentSourceUrl = selectedSource?.url, + onSourceSelected = { source -> + selectedSource = source + invalidate() + } + ) + ) + } + .build() + + paneBuilder.addAction(playAction) + paneBuilder.addAction(sourceAction) + + return PaneTemplate.Builder(paneBuilder.build()) + .setTitle(seriesDetails.name) // Header Title is Series Name + .setHeaderAction(Action.BACK) + .build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/EpisodeListScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/EpisodeListScreen.kt new file mode 100644 index 00000000000..b5bd0eb1b22 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/EpisodeListScreen.kt @@ -0,0 +1,112 @@ +package com.lagradost.cloudstream3.ui.car + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.ActionStrip +import androidx.car.app.model.CarIcon +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import androidx.core.graphics.drawable.IconCompat +import com.lagradost.cloudstream3.TvSeriesLoadResponse +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.getId +import com.lagradost.cloudstream3.ui.result.VideoWatchState +import com.lagradost.cloudstream3.utils.DataStoreHelper.getVideoWatchState +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos + +class EpisodeListScreen( + carContext: CarContext, + private val details: TvSeriesLoadResponse, + private val isExpressMode: Boolean = false +) : Screen(carContext) { + + private val availableSeasons: List = details.episodes.mapNotNull { it.season }.distinct().sorted() + private var currentSeasonIndex: Int = 0 // Index in availableSeasons list + + override fun onGetTemplate(): Template { + if (availableSeasons.isEmpty()) { + return ListTemplate.Builder() + .setTitle(details.name) + .setHeaderAction(Action.BACK) + .setSingleList(ItemList.Builder().addItem(Row.Builder().setTitle(CarStrings.get(R.string.car_no_episodes_found)).build()).build()) + .build() + } + + val currentSeason = availableSeasons[currentSeasonIndex] + val seasonEpisodes = details.episodes.filter { it.season == currentSeason }.sortedBy { it.episode } + + val mainId = details.getId() + val allEpisodesSorted = details.episodes.sortedBy { (it.season?.times(10_000) ?: 0) + (it.episode ?: 0) } + + val listBuilder = ItemList.Builder() + + seasonEpisodes.forEach { episode -> + val globalIndex = allEpisodesSorted.indexOf(episode) + val episodeIndex = episode.episode ?: (globalIndex + 1) + val id = mainId + (episode.season?.times(100_000) ?: 0) + episodeIndex + 1 + val watchState = getVideoWatchState(id) + val isWatched = watchState == VideoWatchState.Watched + + val titleBase = "${episode.episode}. ${episode.name ?: "${CarStrings.get(R.string.car_episode)} ${episode.episode}"}" + val title = if (isWatched) "✅ $titleBase" else titleBase + + val rowBuilder = Row.Builder() + .setTitle(title) + .setOnClickListener { + if (isExpressMode) { + // Get saved position for resume + val startTime = getViewPos(episode.data.hashCode())?.position ?: 0L + screenManager.push( + PlayerCarScreen( + carContext = carContext, + loadResponse = details, + selectedEpisode = episode, + playlist = seasonEpisodes, + startTime = startTime + ) + ) + } else { + screenManager.push( + EpisodeDetailScreen( + carContext = carContext, + seriesDetails = details, + episode = episode, + playlist = seasonEpisodes + ) + ) + } + } + + episode.description?.let { + // Truncate to avoid huge texts, though AA handles some wrapping + val desc = if (it.length > 100) it.substring(0, 97) + "..." else it + rowBuilder.addText(desc) + } + + listBuilder.addItem(rowBuilder.build()) + } + + val seasonAction = Action.Builder() + .setTitle("${CarStrings.get(R.string.car_season)} $currentSeason") + .setOnClickListener { + // Cycle through seasons + currentSeasonIndex = (currentSeasonIndex + 1) % availableSeasons.size + invalidate() + } + .build() + + return ListTemplate.Builder() + .setTitle("${details.name} - ${CarStrings.get(R.string.car_season)} $currentSeason") + .setHeaderAction(Action.BACK) + .setSingleList(listBuilder.build()) + .setActionStrip( + ActionStrip.Builder() + .addAction(seasonAction) + .build() + ) + .build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/HistoryScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/HistoryScreen.kt new file mode 100644 index 00000000000..2e7d92e484b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/HistoryScreen.kt @@ -0,0 +1,218 @@ +package com.lagradost.cloudstream3.ui.car + +import androidx.car.app.CarContext +import android.util.Log +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds +import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.TvSeriesLoadResponse +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.newMovieSearchResponse +import com.lagradost.cloudstream3.newTvSeriesSearchResponse +import com.lagradost.cloudstream3.isMovieType +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.coroutines.delay + +class HistoryScreen(carContext: CarContext) : Screen(carContext), DefaultLifecycleObserver { + private val scope = CoroutineScope(Dispatchers.IO) + private var itemList: ItemList? = null + + init { + lifecycle.addObserver(this) + } + + override fun onStart(owner: LifecycleOwner) { + loadHistory() + } + + private fun loadHistory(retryCount: Int = 0) { + scope.launch { + withContext(Dispatchers.Main) { + itemList = null + invalidate() + } + try { + // Strict logic copied from HomeViewModel.getResumeWatching() + val resumeWatchingResult = withContext(Dispatchers.IO) { + val ids = getAllResumeStateIds() + Log.d("HistoryDebug", "Loading history. IDs found: ${ids?.size ?: 0}") + ids?.mapNotNull { id -> + getLastWatched(id) + }?.sortedBy { -it.updateTime }?.mapNotNull { resume -> + val data = getKey( + DOWNLOAD_HEADER_CACHE, + resume.parentId.toString() + ) + if (data == null) { + Log.e("HistoryDebug", "MISSING HEADER for parentId: ${resume.parentId}") + return@mapNotNull null + } + Log.d("HistoryDebug", "Found HEADER for parentId: ${resume.parentId} -> ${data.name}") + + Pair(resume, data) + } + } + + val builder = ItemList.Builder() + + if (resumeWatchingResult.isNullOrEmpty()) { + builder.setNoItemsMessage(CarStrings.get(R.string.car_no_continue_watching)) + } else { + resumeWatchingResult.forEach { (resume, cachedData) -> + val title = cachedData.name + val subtitle = if (resume.episode != null && resume.season != null) { + "S${resume.season}E${resume.episode} (${cachedData.apiName})" + } else { + cachedData.apiName + } + + builder.addItem( + Row.Builder() + .setTitle(title) + .addText(subtitle) + .setOnClickListener { + playResumeItem(resume, cachedData) + } + .build() + ) + } + } + + val builtList = builder.build() + withContext(Dispatchers.Main) { + itemList = builtList + invalidate() + } + } catch (e: Exception) { + if (retryCount < 3) { + delay(3000) + loadHistory(retryCount + 1) + } else { + withContext(Dispatchers.Main) { + itemList = ItemList.Builder() + .addItem( + Row.Builder() + .setTitle("Errore: ${e.message}") + .setOnClickListener { loadHistory() } + .build() + ) + .build() + invalidate() + } + } + } + } + } + + private fun playResumeItem(resume: VideoDownloadHelper.ResumeWatching, cachedData: VideoDownloadHelper.DownloadHeaderCached) { + scope.launch { + withContext(Dispatchers.Main) { + androidx.car.app.CarToast.makeText(carContext, CarStrings.get(R.string.car_resuming, cachedData.name), androidx.car.app.CarToast.LENGTH_LONG).show() + } + + val api = getApiFromNameNull(cachedData.apiName) ?: return@launch + val repo = APIRepository(api) + + val loadResult = when(val result = repo.load(cachedData.url)) { + is Resource.Success -> result.value + else -> null + } ?: return@launch + + if (loadResult is TvSeriesLoadResponse) { + // Find the specific episode to resume + // resume.episodeId should match episode.data.hashCode() used by PlayerCarScreen + val episodeToResume = loadResult.episodes.find { episode -> + // Promiscuous check: try multiple ways to match the episode + // Note: Episode class does not have an 'id' field, so we skip direct ID check + val urlHashMatch = episode.data.hashCode() == resume.episodeId + val numberMatch = episode.episode == resume.episode && episode.season == resume.season + + urlHashMatch || numberMatch + } + + if (episodeToResume != null) { + val startTime = getViewPos(resume.episodeId)?.position ?: 0L + val seasonEpisodes = loadResult.episodes.filter { it.season == episodeToResume.season } + + withContext(Dispatchers.Main) { + screenManager.push( + PlayerCarScreen( + carContext = carContext, + loadResponse = loadResult, + selectedEpisode = episodeToResume, + playlist = seasonEpisodes, + startTime = startTime + ) + ) + } + } else { + // Fallback to episode list if episode not found + withContext(Dispatchers.Main) { + screenManager.push(EpisodeListScreen(carContext, loadResult, isExpressMode = true)) + } + } + } else { + val startTime = getViewPos(resume.episodeId)?.position ?: 0L + + // Prepare SearchResponse item for PlayerCarScreen + val item: SearchResponse = if (cachedData.type.isMovieType()) { + api.newMovieSearchResponse( + name = cachedData.name, + url = cachedData.url, + type = cachedData.type, + ) { + this.posterUrl = cachedData.poster + } + } else { + api.newTvSeriesSearchResponse( + name = cachedData.name, + url = cachedData.url, + type = cachedData.type, + ) { + this.posterUrl = cachedData.poster + } + } + + withContext(Dispatchers.Main) { + screenManager.push( + PlayerCarScreen( + carContext = carContext, + item = item, + loadResponse = loadResult, + selectedEpisode = null, + startTime = startTime + ) + ) + } + } + } + } + + override fun onGetTemplate(): Template { + val list = itemList ?: ItemList.Builder().setNoItemsMessage(CarStrings.get(R.string.car_loading)).build() + + return ListTemplate.Builder() + .setTitle(CarStrings.get(R.string.car_history)) + .setHeaderAction(Action.BACK) + .setSingleList(list) + .build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/LibraryCarScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/LibraryCarScreen.kt new file mode 100644 index 00000000000..50dbb980a26 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/LibraryCarScreen.kt @@ -0,0 +1,44 @@ +package com.lagradost.cloudstream3.ui.car + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData +import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class LibraryCarScreen(carContext: CarContext) : Screen(carContext) { + + override fun onGetTemplate(): Template { + return ListTemplate.Builder() + .setSingleList( + ItemList.Builder() + .addItem( + Row.Builder() + .setTitle(CarStrings.get(R.string.car_bookmarks)) + .setOnClickListener { screenManager.push(BookmarksScreen(carContext)) } + .setBrowsable(true) + .build() + ) + .addItem( + Row.Builder() + .setTitle(CarStrings.get(R.string.car_history)) + .setOnClickListener { screenManager.push(HistoryScreen(carContext)) } + .setBrowsable(true) + .build() + ) + .build() + ) + .setTitle(CarStrings.get(R.string.car_library)) + .setHeaderAction(Action.BACK) + .build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/MainCarScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/MainCarScreen.kt new file mode 100644 index 00000000000..f3628e31835 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/MainCarScreen.kt @@ -0,0 +1,228 @@ +package com.lagradost.cloudstream3.ui.car + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.ActionStrip +import androidx.car.app.model.CarIcon +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.SectionedItemList +import androidx.car.app.model.Template +import androidx.core.graphics.drawable.IconCompat +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.HomePageList +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.utils.DataStoreHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class MainCarScreen(carContext: CarContext) : Screen(carContext), DefaultLifecycleObserver { + private var homePageLists: List = emptyList() + private var isLoading = true + private var errorMessage: String? = null + private var currentApiName: String = "" + + companion object { + private const val LOADING_DELAY_MS = 500L + private const val MAX_LOADING_ATTEMPTS = 20 + } + + init { + lifecycle.addObserver(this) + } + + override fun onResume(owner: LifecycleOwner) { + super.onResume(owner) + // Reload data on resume to catch provider changes + loadData() + } + + private fun loadData() { + isLoading = true + errorMessage = null + currentApiName = DataStoreHelper.currentHomePage ?: "" + invalidate() + + CoroutineScope(Dispatchers.IO).launch { + try { + var api = getApiFromNameNull(currentApiName) + var attempts = 0 + // Retry waiting for plugins to load + while (api == null && attempts < MAX_LOADING_ATTEMPTS) { + delay(LOADING_DELAY_MS) + api = getApiFromNameNull(currentApiName) + attempts++ + } + + if (api == null) { + errorMessage = "${CarStrings.get(R.string.car_provider_not_found)}: $currentApiName" + isLoading = false + invalidate() + return@launch + } + + val repo = APIRepository(api) + when (val result = repo.getMainPage(1, null)) { + is Resource.Success -> { + homePageLists = result.value.filterNotNull().flatMap { it.items } + isLoading = false + } + is Resource.Failure -> { + errorMessage = result.errorString ?: CarStrings.get(R.string.car_loading_content) + isLoading = false + } + is Resource.Loading -> {} + } + } catch (e: Exception) { + errorMessage = e.message + isLoading = false + } + invalidate() + } + } + + override fun onGetTemplate(): Template { + return ListTemplate.Builder() + .setTitle(carContext.getString(R.string.app_name)) + .addSectionedList( + SectionedItemList.create( + buildMenuSection(), + CarStrings.get(R.string.car_menu) + ) + ) + .addSectionedList( + SectionedItemList.create( + buildContentSection(), + CarStrings.get(R.string.car_home_content) + ) + ) + .setActionStrip(buildActionStrip()) + .build() + } + + private fun buildMenuSection(): ItemList { + val menuListBuilder = ItemList.Builder() + + menuListBuilder.addItem( + createMenuRow( + title = CarStrings.get(R.string.car_favorites), + iconRes = R.drawable.ic_baseline_favorite_24, + screen = { BookmarksScreen(carContext) } + ) + ) + + menuListBuilder.addItem( + createMenuRow( + title = CarStrings.get(R.string.car_history), + iconRes = android.R.drawable.ic_menu_recent_history, + screen = { HistoryScreen(carContext) } + ) + ) + + menuListBuilder.addItem( + createMenuRow( + title = CarStrings.get(R.string.car_downloads), + iconRes = android.R.drawable.stat_sys_download, + screen = { DownloadsScreen(carContext) } + ) + ) + + menuListBuilder.addItem( + Row.Builder() + .setTitle(CarStrings.get(R.string.car_provider)) + .addText("${CarStrings.get(R.string.car_current)}: $currentApiName") + .setImage(CarIcon.Builder(IconCompat.createWithResource(carContext, android.R.drawable.ic_menu_manage)).build()) + .setOnClickListener { screenManager.push(ProviderCarScreen(carContext)) } + .setBrowsable(true) + .build() + ) + + menuListBuilder.addItem( + Row.Builder() + .setTitle(CarStrings.get(R.string.car_player_mode)) + .addText("${CarStrings.get(R.string.car_current)}: ${ + if (DataStoreHelper.carPlayerMode == 0) CarStrings.get(R.string.car_player_mode_advanced) + else CarStrings.get(R.string.car_player_mode_simple) + }") + .setImage(CarIcon.Builder(IconCompat.createWithResource(carContext, android.R.drawable.ic_menu_preferences)).build()) + .setOnClickListener { + DataStoreHelper.carPlayerMode = if (DataStoreHelper.carPlayerMode == 0) 1 else 0 + invalidate() + } + .build() + ) + + menuListBuilder.addItem( + createMenuRow( + title = CarStrings.get(R.string.car_about_me), + iconRes = android.R.drawable.ic_menu_info_details, + screen = { AboutMeScreen(carContext) } + ) + ) + + return menuListBuilder.build() + } + + private fun createMenuRow(title: String, iconRes: Int, screen: () -> Screen): Row { + return Row.Builder() + .setTitle(title) + .setImage(CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()) + .setOnClickListener { screenManager.push(screen()) } + .setBrowsable(true) + .build() + } + + private fun buildContentSection(): ItemList { + val contentListBuilder = ItemList.Builder() + + if (isLoading) { + contentListBuilder.addItem(Row.Builder().setTitle(CarStrings.get(R.string.car_loading)).setBrowsable(false).build()) + } else if (errorMessage != null) { + contentListBuilder.addItem(Row.Builder().setTitle("${CarStrings.get(R.string.car_error)}: $errorMessage").setBrowsable(false).build()) + } else if (homePageLists.isEmpty()) { + contentListBuilder.addItem(Row.Builder().setTitle(CarStrings.get(R.string.car_no_content_from_provider)).setBrowsable(false).build()) + } else { + homePageLists.forEach { homePageList -> + contentListBuilder.addItem( + Row.Builder() + .setTitle(homePageList.name) + .setOnClickListener { + screenManager.push(CategoryScreen(carContext, homePageList)) + } + .setBrowsable(true) + .build() + ) + } + } + return contentListBuilder.build() + } + + private fun buildActionStrip(): ActionStrip { + return ActionStrip.Builder() + .addAction( + Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(carContext, android.R.drawable.ic_menu_search)).build()) + .setOnClickListener { + screenManager.push(SearchCarScreen(carContext)) + } + .build() + ) + .addAction( + Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(carContext, R.drawable.ic_refresh)).build()) + .setOnClickListener { + loadData() + } + .build() + ) + .build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/PlayerCarScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/PlayerCarScreen.kt new file mode 100644 index 00000000000..d0e9ba30dce --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/PlayerCarScreen.kt @@ -0,0 +1,442 @@ +package com.lagradost.cloudstream3.ui.car + +import android.util.Log +import androidx.car.app.AppManager +import androidx.car.app.CarContext +import androidx.car.app.CarToast +import androidx.car.app.Screen +import androidx.car.app.SurfaceCallback +import androidx.car.app.SurfaceContainer +import androidx.car.app.model.Template +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.media3.common.AudioAttributes +import androidx.media3.common.C +import androidx.media3.common.MediaItem +import androidx.media3.common.MimeTypes +import androidx.media3.common.Player +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.session.MediaSession +import com.lagradost.cloudstream3.Episode +import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.MovieLoadResponse +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.TvSeriesLoadResponse +import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.AnimeLoadResponse +import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.ui.result.getId +import com.lagradost.cloudstream3.utils.CarHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.PlayerCarHelper +import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class PlayerCarScreen( + carContext: CarContext, + val item: SearchResponse? = null, + val loadResponse: LoadResponse? = null, + val selectedEpisode: Episode? = null, + val playlist: List? = null, + var startTime: Long = 0L, + val fileUri: String? = null, + val videoId: Int? = null, + val parentId: Int? = null, + val preSelectedSource: ExtractorLink? = null +) : Screen(carContext), SurfaceCallback { + + companion object { + private const val TAG = "PlayerCarScreen" + } + + private var activeEpisode: Episode? = selectedEpisode + private val scope = CoroutineScope(Dispatchers.IO + Job()) + private var player: ExoPlayer? = null + private var mediaSession: MediaSession? = null + private var isPlaying = true + private var isFillMode = false + + private var currentEpisodeId: Int? = null + private var currentParentId: Int? = null + private var saveProgressJob: Job? = null + + // Shared callbacks for strategy-driven template building + private val templateCallbacks = TemplateCallbacks( + carContext = carContext, + getIsPlaying = { isPlaying }, + getIsFillMode = { isFillMode }, + isNextEnabled = { playlist != null && activeEpisode != null && playlist.indexOf(activeEpisode) < playlist.size - 1 }, + onExit = { screenManager.pop() }, + onPlayPause = { + if (isPlaying) { player?.pause(); saveProgress() } else { player?.play() } + }, + onSeekBack = ::seekBack, + onSeekForward = ::seekForward, + onToggleFillMode = { + isFillMode = !isFillMode + surfaceStrategy.applyVideoScale(isFillMode) + showToast( + if (!isFillMode) CarStrings.get(R.string.car_fit_to_screen) + else CarStrings.get(R.string.car_fill_screen) + ) + }, + onNextEpisode = { scope.launch { loadNextEpisode() } }, + onInvalidate = ::invalidate + ) + + // Surface rendering strategy (Advanced or Simple) + private val surfaceStrategy: PlayerSurfaceStrategy = if (DataStoreHelper.carPlayerMode == 0) { + PresentationSurfaceStrategy( + context = carContext, + callbacks = templateCallbacks, + getPlayer = { player }, + getIsPlaying = { isPlaying }, + onSeekBack = ::seekBack, + onSeekForward = ::seekForward, + onSaveProgress = ::saveProgress, + getTitle = { item?.name ?: activeEpisode?.name ?: "Unknown" }, + getSubtitle = { activeEpisode?.let { ep -> "S${ep.season} E${ep.episode} - ${ep.name}" } }, + onError = ::showToast + ) + } else { + DirectSurfaceStrategy(callbacks = templateCallbacks, getPlayer = { player }) + } + + init { + lifecycle.addObserver(object : LifecycleEventObserver { + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + when (event) { + Lifecycle.Event.ON_START -> { + carContext.getCarService(AppManager::class.java).setSurfaceCallback(this@PlayerCarScreen) + player?.playWhenReady = true + } + Lifecycle.Event.ON_STOP -> { + player?.playWhenReady = false + saveProgress() + carContext.getCarService(AppManager::class.java).setSurfaceCallback(null) + } + Lifecycle.Event.ON_DESTROY -> { + surfaceStrategy.release() + mediaSession?.release() + mediaSession = null + player?.release() + player = null + scope.cancel() + } + else -> {} + } + } + }) + + if (fileUri != null) { + currentEpisodeId = videoId + currentParentId = parentId + if (activeEpisode == null) { + scope.launch(Dispatchers.IO) { + populateEpisodeFromDownloadCache() + withContext(Dispatchers.Main) { startPlayback(fileUri) } + } + } else { + scope.launch { startPlayback(fileUri) } + } + } else { + loadMedia(item?.url) + } + } + + // --- SurfaceCallback --- + + override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) { + surfaceStrategy.onSurfaceAvailable(surfaceContainer) + } + + override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) { + surfaceStrategy.onSurfaceDestroyed(surfaceContainer) + } + + override fun onClick(x: Float, y: Float) { + surfaceStrategy.onClick(x, y) + } + + // --- Template --- + + override fun onGetTemplate(): Template = surfaceStrategy.buildTemplate() + + // --- Media Loading --- + + private fun loadMedia(url: String?) { + updateStatus(CarStrings.get(R.string.car_loading)) + scope.launch { + val data = getLoadResponse() + + if (data == null) { + if (item?.type == TvType.Live) { + Log.d(TAG, "Details load failed, attempting direct playback for Live content") + withContext(Dispatchers.Main) { startPlayback(item.url) } + return@launch + } + showToast(CarStrings.get(R.string.car_unable_to_load_details)) + return@launch + } + + resolveIds(data) + loadLinks(data) + } + } + + private suspend fun getLoadResponse(): LoadResponse? { + if (loadResponse != null) return loadResponse + val searchItem = item ?: return null + val api = getApiFromNameNull(searchItem.apiName) ?: return null + return try { + when (val result = APIRepository(api).load(searchItem.url)) { + is Resource.Success -> result.value + else -> null + } + } catch (e: Exception) { + Log.e(TAG, "Error loading details for ${searchItem.url}", e) + null + } + } + + private fun resolveIds(data: LoadResponse) { + currentParentId = item?.id ?: data.getId() + PlayerCarHelper.saveHeaderCache(item, data, currentParentId) + + currentEpisodeId = if (data is TvSeriesLoadResponse && activeEpisode != null) { + CarHelper.generateConsistentEpisodeId(activeEpisode!!, data) + } else { + currentParentId + } + + Log.d(TAG, "Resolved IDs - Name: ${data.name} | Parent: $currentParentId | Episode: $currentEpisodeId") + } + + private suspend fun loadLinks(data: LoadResponse) { + val api = getApiFromNameNull(data.apiName) ?: return + val links = mutableListOf() + try { + val urlToLoad = when { + activeEpisode != null -> activeEpisode!!.data + data is TvSeriesLoadResponse -> data.episodes.firstOrNull()?.data + data is AnimeLoadResponse -> data.episodes.values.flatten().firstOrNull()?.data + data is MovieLoadResponse -> data.dataUrl + else -> data.url + } + + if (urlToLoad == null) { + showToast(CarStrings.get(R.string.car_no_playable_content)) + return + } + + if (preSelectedSource != null) { + startPlayback(preSelectedSource) + } else { + api.loadLinks(urlToLoad, false, {}, { link -> links.add(link) }) + if (links.isNotEmpty()) { + startPlayback(links.sortedByDescending { it.quality }.first()) + } else { + showToast(CarStrings.get(R.string.car_no_link_found)) + } + } + } catch (e: Exception) { + showToast("${CarStrings.get(R.string.car_error_loading_links)}: ${e.message}") + e.printStackTrace() + } + } + + // --- Playback --- + + @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) + private suspend fun startPlayback(link: Any) { + val url = when (link) { + is ExtractorLink -> link.url + is String -> link + else -> return + } + + withContext(Dispatchers.Main) { + updateStatus(CarStrings.get(R.string.car_starting_playback)) + if (player == null) { + player = ExoPlayer.Builder(carContext).build().apply { + val audioAttributes = AudioAttributes.Builder() + .setUsage(C.USAGE_MEDIA) + .setContentType(C.AUDIO_CONTENT_TYPE_MOVIE) + .build() + setAudioAttributes(audioAttributes, true) + } + } + player?.let { p -> + setupMediaSession(p) + setupPlaybackListener(p) + + surfaceStrategy.attachPlayer(p) + surfaceStrategy.applyVideoScale(isFillMode) + + val mediaMetadata = PlayerCarHelper.createMediaMetadata(carContext, item, activeEpisode) + val mediaItemBuilder = MediaItem.Builder() + .setUri(url) + .setMediaMetadata(mediaMetadata) + + if (link is ExtractorLink && link.isM3u8) { + mediaItemBuilder.setMimeType(MimeTypes.APPLICATION_M3U8) + } + + p.setMediaItem(mediaItemBuilder.build()) + p.prepare() + if (startTime > 0L) p.seekTo(startTime) + p.play() + updateStatus(CarStrings.get(R.string.car_playing)) + } + } + } + + private fun setupMediaSession(p: ExoPlayer) { + if (mediaSession != null) return + + val forwardingPlayer = object : androidx.media3.common.ForwardingPlayer(p) { + override fun seekToNext() { + scope.launch { loadNextEpisode() } + } + override fun seekToPrevious() { + seekBack() + } + } + + mediaSession = MediaSession.Builder(carContext, forwardingPlayer) + .setCallback(object : MediaSession.Callback { + override fun onConnect( + session: MediaSession, + controller: MediaSession.ControllerInfo + ): MediaSession.ConnectionResult { + val commands = Player.Commands.Builder() + .add(Player.COMMAND_PLAY_PAUSE) + .add(Player.COMMAND_STOP) + .add(Player.COMMAND_SEEK_TO_NEXT) + .add(Player.COMMAND_SEEK_TO_PREVIOUS) + .build() + return MediaSession.ConnectionResult.AcceptedResultBuilder(session) + .setAvailablePlayerCommands(commands) + .build() + } + }) + .build() + } + + private fun setupPlaybackListener(p: ExoPlayer) { + p.addListener(object : Player.Listener { + override fun onIsPlayingChanged(playing: Boolean) { + isPlaying = playing + invalidate() + surfaceStrategy.updatePlayPauseState() + + saveProgressJob?.cancel() + if (playing) { + saveProgressJob = scope.launch { + while (true) { + delay(1000) + withContext(Dispatchers.Main) { + player?.let { surfaceStrategy.updateProgress(it.currentPosition, it.duration) } + if (System.currentTimeMillis() % 10000 < 1000) saveProgress() + } + } + } + } else { + saveProgress() + } + } + }) + } + + // --- Seek & Episode Navigation --- + + private fun seekBack() { + player?.let { p -> + val newPos = (p.currentPosition - 30_000).coerceAtLeast(0) + p.seekTo(newPos) + updateStatus("-30s") + } + } + + private fun seekForward() { + player?.let { p -> + val newPos = (p.currentPosition + 30_000).coerceAtMost(p.duration) + p.seekTo(newPos) + updateStatus("+30s") + } + } + + private suspend fun loadNextEpisode() { + if (playlist == null || activeEpisode == null) { + showToast("No playlist available") + return + } + + val currentIndex = playlist.indexOfFirst { it.data == activeEpisode!!.data } + if (currentIndex != -1 && currentIndex < playlist.size - 1) { + val nextEp = playlist[currentIndex + 1] + activeEpisode = nextEp + val data = getLoadResponse() + currentEpisodeId = CarHelper.generateConsistentEpisodeId(nextEp, data) ?: nextEp.data.hashCode() + startTime = 0L + + showToast("Loading: ${nextEp.name}") + withContext(Dispatchers.Main) { loadMedia(nextEp.data) } + } else { + showToast("No more episodes") + } + } + + // --- Progress --- + + private fun saveProgress() { + val p = player ?: return + PlayerCarHelper.saveProgress(p.currentPosition, p.duration, item, activeEpisode, currentEpisodeId, currentParentId) + } + + + + // --- Helpers --- + + private fun populateEpisodeFromDownloadCache() { + try { + val cachedEp = carContext.getKey( + DOWNLOAD_EPISODE_CACHE, videoId.toString() + ) + if (cachedEp != null) { + @Suppress("DEPRECATION_ERROR") + activeEpisode = Episode( + data = "", + name = cachedEp.name, + season = cachedEp.season, + episode = cachedEp.episode, + posterUrl = cachedEp.poster + ) + Log.d(TAG, "Populated activeEpisode from download: S${cachedEp.season} E${cachedEp.episode}") + } + } catch (e: Exception) { + Log.e(TAG, "Error loading download metadata", e) + } + } + + private fun updateStatus(msg: String) { + if (msg != "Playing") showToast(msg) + } + + private fun showToast(msg: String) { + CarToast.makeText(carContext, msg, CarToast.LENGTH_LONG).show() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/PlayerSurfaceStrategy.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/PlayerSurfaceStrategy.kt new file mode 100644 index 00000000000..ac97af8483d --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/PlayerSurfaceStrategy.kt @@ -0,0 +1,340 @@ +package com.lagradost.cloudstream3.ui.car + +import android.hardware.display.DisplayManager +import android.hardware.display.VirtualDisplay +import android.util.Log +import android.view.ContextThemeWrapper +import android.view.Surface +import androidx.car.app.SurfaceContainer +import androidx.car.app.model.Action +import androidx.car.app.model.ActionStrip +import androidx.car.app.model.CarColor +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.media3.exoplayer.ExoPlayer +import com.lagradost.cloudstream3.R + +/** + * Strategy interface for managing the surface rendering pipeline. + * Implementations handle how the video is displayed on the car screen + * and how the ActionStrip template is built. + */ +interface PlayerSurfaceStrategy { + fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) + fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) + fun onClick(x: Float, y: Float) + fun attachPlayer(player: ExoPlayer) + fun applyVideoScale(fill: Boolean) + fun updateProgress(current: Long, total: Long) + fun updatePlayPauseState() + fun buildTemplate(): Template + fun release() +} + +/** + * Holds dynamic state and callbacks needed by each strategy to build its template. + * Injected once at construction time; lambdas keep values current. + */ +class TemplateCallbacks( + val carContext: android.content.Context, + val getIsPlaying: () -> Boolean, + val getIsFillMode: () -> Boolean, + val isNextEnabled: () -> Boolean, + val onExit: () -> Unit, + val onPlayPause: () -> Unit, + val onSeekBack: () -> Unit, + val onSeekForward: () -> Unit, + val onToggleFillMode: () -> Unit, + val onNextEpisode: () -> Unit, + val onInvalidate: () -> Unit +) + +// ───────────────────────────────────────────────────── +// Advanced Player (VirtualDisplay + Presentation overlay) +// ───────────────────────────────────────────────────── + +/** + * Advanced player mode: renders through a VirtualDisplay + CarPlayerPresentation overlay. + * Provides on-screen touch controls (play/pause, seek, progress bar, title). + * The ActionStrip is minimal (Exit, Resize, Next) since the overlay handles interaction. + */ +class PresentationSurfaceStrategy( + private val context: android.content.Context, + private val callbacks: TemplateCallbacks, + private val getPlayer: () -> ExoPlayer?, + private val getIsPlaying: () -> Boolean, + private val onSeekBack: () -> Unit, + private val onSeekForward: () -> Unit, + private val onSaveProgress: () -> Unit, + private val getTitle: () -> String, + private val getSubtitle: () -> String?, + private val onError: (String) -> Unit +) : PlayerSurfaceStrategy { + + companion object { + private const val TAG = "PresentationStrategy" + } + + private var virtualDisplay: VirtualDisplay? = null + private var presentation: CarPlayerPresentation? = null + + override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) { + surfaceContainer.surface?.let { surface -> + setupVirtualDisplay(surface, surfaceContainer.width, surfaceContainer.height, surfaceContainer.dpi) + } + } + + override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) { + releaseVirtualDisplay() + } + + override fun onClick(x: Float, y: Float) { + presentation?.dispatchTouch(x, y) + } + + override fun attachPlayer(player: ExoPlayer) { + presentation?.getTextureView()?.let { player.setVideoTextureView(it) } + } + + override fun applyVideoScale(fill: Boolean) { + presentation?.applyVideoScale(fill) + } + + override fun updateProgress(current: Long, total: Long) { + presentation?.updateProgress(current, total) + } + + override fun updatePlayPauseState() { + presentation?.updatePlayPauseState() + } + + /** Minimal ActionStrip: [Exit] [Resize] [Next Episode] */ + override fun buildTemplate(): Template { + val ctx = callbacks.carContext + val actionStripBuilder = ActionStrip.Builder() + + val exitAction = Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(ctx, R.drawable.ic_baseline_arrow_back_24)).build()) + .setOnClickListener { callbacks.onExit() } + .build() + + val resizeAction = Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(ctx, R.drawable.ic_baseline_aspect_ratio_24)).build()) + .setOnClickListener { callbacks.onToggleFillMode() } + .build() + + val nextAction = Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(ctx, R.drawable.ic_baseline_skip_next_24)).build()) + .setOnClickListener { callbacks.onNextEpisode() } + .setEnabled(callbacks.isNextEnabled()) + .build() + + actionStripBuilder.addAction(exitAction) + actionStripBuilder.addAction(resizeAction) + actionStripBuilder.addAction(nextAction) + + return NavigationTemplate.Builder() + .setActionStrip(actionStripBuilder.build()) + .setBackgroundColor(CarColor.createCustom(android.graphics.Color.BLACK, android.graphics.Color.BLACK)) + .build() + } + + override fun release() { + releaseVirtualDisplay() + } + + private fun setupVirtualDisplay(surface: Surface, width: Int, height: Int, dpi: Int) { + try { + val displayManager = context.getSystemService(android.content.Context.DISPLAY_SERVICE) as DisplayManager + Log.d(TAG, "Creating VirtualDisplay: ${width}x${height} @ $dpi") + + virtualDisplay = displayManager.createVirtualDisplay( + "CloudStreamCarPlayer", width, height, dpi, surface, + DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION or DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY + ) + + val display = virtualDisplay?.display ?: return + val themeContext = ContextThemeWrapper(context, R.style.AppTheme) + presentation = CarPlayerPresentation( + context = themeContext, + display = display, + getPlayer = getPlayer, + getIsPlaying = getIsPlaying, + onSeekBack = onSeekBack, + onSeekForward = onSeekForward, + onSaveProgress = onSaveProgress, + getTitle = getTitle, + getSubtitle = getSubtitle + ) + try { + presentation?.show() + } catch (e: android.view.WindowManager.InvalidDisplayException) { + Log.e(TAG, "Invalid display for presentation", e) + } + } catch (e: Exception) { + Log.e(TAG, "Error setting up VirtualDisplay", e) + onError("Error starting display: ${e.message}") + // Fallback: direct surface + getPlayer()?.setVideoSurface(surface) + } + } + + private fun releaseVirtualDisplay() { + presentation?.getTextureView()?.let { getPlayer()?.clearVideoTextureView(it) } + presentation?.dismiss() + presentation = null + virtualDisplay?.release() + virtualDisplay = null + } +} + +// ───────────────────────────────────────────────────── +// Simple Player (direct surface, ActionStrip-only controls) +// ───────────────────────────────────────────────────── + +/** + * Simple player mode: renders video directly to the car surface. + * No on-screen overlay — playback is controlled entirely via ActionStrip. + * + * ActionStrip has two states toggled by [showSeekControls]: + * - Default: [Exit] [Play/Pause] [Next Episode] [Seek Controls] + * - Seek: [Back] [-30s] [+30s] [Resize] + */ +class DirectSurfaceStrategy( + private val callbacks: TemplateCallbacks, + private val getPlayer: () -> ExoPlayer? +) : PlayerSurfaceStrategy { + + private var surface: Surface? = null + private var showSeekControls = false + + override fun onSurfaceAvailable(surfaceContainer: SurfaceContainer) { + surfaceContainer.surface?.let { + surface = it + getPlayer()?.setVideoSurface(it) + } + } + + override fun onSurfaceDestroyed(surfaceContainer: SurfaceContainer) { + surface = null + getPlayer()?.setVideoSurface(null) + } + + override fun onClick(x: Float, y: Float) { + // No overlay in simple mode + } + + override fun attachPlayer(player: ExoPlayer) { + surface?.let { player.setVideoSurface(it) } + } + + @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) + override fun applyVideoScale(fill: Boolean) { + getPlayer()?.videoScalingMode = if (fill) { + androidx.media3.common.C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING + } else { + androidx.media3.common.C.VIDEO_SCALING_MODE_SCALE_TO_FIT + } + } + + override fun updateProgress(current: Long, total: Long) { + // No overlay to update + } + + override fun updatePlayPauseState() { + // No overlay to update + } + + /** Full legacy ActionStrip with seek toggle */ + override fun buildTemplate(): Template { + val ctx = callbacks.carContext + val actionStripBuilder = ActionStrip.Builder() + + if (showSeekControls) { + buildSeekModeStrip(ctx, actionStripBuilder) + } else { + buildDefaultModeStrip(ctx, actionStripBuilder) + } + + return NavigationTemplate.Builder() + .setActionStrip(actionStripBuilder.build()) + .setBackgroundColor(CarColor.createCustom(android.graphics.Color.BLACK, android.graphics.Color.BLACK)) + .build() + } + + /** SEEK MODE: [Back (to menu)] [-30s] [+30s] [Resize] */ + private fun buildSeekModeStrip(ctx: android.content.Context, builder: ActionStrip.Builder) { + val backToMenuAction = Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(ctx, R.drawable.ic_baseline_arrow_back_24)).build()) + .setOnClickListener { + showSeekControls = false + callbacks.onInvalidate() + } + .build() + + val seekBackAction = Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(ctx, R.drawable.go_back_30)).build()) + .setOnClickListener { callbacks.onSeekBack() } + .build() + + val seekForwardAction = Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(ctx, R.drawable.go_forward_30)).build()) + .setOnClickListener { callbacks.onSeekForward() } + .build() + + val resizeAction = Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(ctx, R.drawable.ic_baseline_aspect_ratio_24)).build()) + .setOnClickListener { callbacks.onToggleFillMode() } + .build() + + builder.addAction(backToMenuAction) + .addAction(seekBackAction) + .addAction(seekForwardAction) + .addAction(resizeAction) + } + + /** DEFAULT MODE: [Exit] [Play/Pause] [Next Episode] [Seek Controls] */ + private fun buildDefaultModeStrip(ctx: android.content.Context, builder: ActionStrip.Builder) { + val exitAction = Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(ctx, R.drawable.ic_baseline_arrow_back_24)).build()) + .setOnClickListener { callbacks.onExit() } + .build() + + val playPauseAction = Action.Builder() + .setIcon( + CarIcon.Builder( + IconCompat.createWithResource( + ctx, + if (callbacks.getIsPlaying()) R.drawable.ic_baseline_pause_24 else R.drawable.ic_baseline_play_arrow_24 + ) + ).build() + ) + .setOnClickListener { callbacks.onPlayPause() } + .build() + + val nextAction = Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(ctx, R.drawable.ic_baseline_skip_next_24)).build()) + .setOnClickListener { callbacks.onNextEpisode() } + .setEnabled(callbacks.isNextEnabled()) + .build() + + val openSeekAction = Action.Builder() + .setIcon(CarIcon.Builder(IconCompat.createWithResource(ctx, R.drawable.ic_baseline_tune_24)).build()) + .setOnClickListener { + showSeekControls = true + callbacks.onInvalidate() + } + .build() + + builder.addAction(exitAction) + .addAction(playPauseAction) + .addAction(nextAction) + .addAction(openSeekAction) + } + + override fun release() { + surface = null + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/ProviderCarScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/ProviderCarScreen.kt new file mode 100644 index 00000000000..e2f86a2f4e4 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/ProviderCarScreen.kt @@ -0,0 +1,42 @@ +package com.lagradost.cloudstream3.ui.car + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.APIHolder.apis +import com.lagradost.cloudstream3.utils.DataStoreHelper + +class ProviderCarScreen(carContext: CarContext) : Screen(carContext) { + override fun onGetTemplate(): Template { + val listBuilder = ItemList.Builder() + + // Filter APIs mostly for simplicity, or show all + val current = DataStoreHelper.currentHomePage + + apis.forEach { api -> + val row = Row.Builder() + .setTitle(api.name) + .setOnClickListener { + DataStoreHelper.currentHomePage = api.name + screenManager.pop() // Go back to Home which will reload + } + + if (api.name == current) { + row.addText(CarStrings.get(R.string.car_selected)) + } + + listBuilder.addItem(row.build()) + } + + return ListTemplate.Builder() + .setSingleList(listBuilder.build()) + .setTitle(CarStrings.get(R.string.car_select_provider)) + .setHeaderAction(Action.BACK) + .build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/SearchCarScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/SearchCarScreen.kt new file mode 100644 index 00000000000..f56be752b4c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/SearchCarScreen.kt @@ -0,0 +1,129 @@ +package com.lagradost.cloudstream3.ui.car + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.CarIcon +import androidx.car.app.model.ItemList +import androidx.car.app.model.Row +import androidx.car.app.model.SearchTemplate +import androidx.car.app.model.Template +import androidx.core.graphics.drawable.IconCompat +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.utils.DataStoreHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.delay +import androidx.car.app.CarToast +import coil3.request.ImageRequest +import coil3.SingletonImageLoader +import coil3.asDrawable +import androidx.core.graphics.drawable.toBitmap + + +class SearchCarScreen(carContext: CarContext) : Screen(carContext) { + + private var itemList: ItemList? = null + private var searchJob: Job? = null + + private val searchCallback = object : SearchTemplate.SearchCallback { + override fun onSearchTextChanged(searchText: String) { + searchJob?.cancel() + if (searchText.isBlank()) { + itemList = null + invalidate() + return + } + searchJob = CoroutineScope(Dispatchers.Main).launch { + delay(400) + performSearch(searchText) + } + } + + override fun onSearchSubmitted(searchText: String) { + performSearch(searchText) + } + } + + private fun performSearch(query: String) { + searchJob?.cancel() + searchJob = CoroutineScope(Dispatchers.IO).launch { + val apiName = DataStoreHelper.currentHomePage ?: return@launch + val api = getApiFromNameNull(apiName) ?: return@launch + val repo = APIRepository(api) + + // Check if search requires page + when(val result = repo.search(query, 1)) { + is Resource.Success -> { + val builder = ItemList.Builder() + val items = result.value.items + if (items.isEmpty()) { + builder.setNoItemsMessage(CarStrings.get(R.string.car_no_results_found)) + } + + // Text-only results + items.map { item -> + val image = try { + if (!item.posterUrl.isNullOrEmpty()) { + val request = ImageRequest.Builder(carContext) + .data(item.posterUrl) + .size(128, 128) + .build() + val result = SingletonImageLoader.get(carContext).execute(request) + val bitmap = result.image?.asDrawable(carContext.resources)?.toBitmap() + if (bitmap != null) { + CarIcon.Builder(IconCompat.createWithBitmap(bitmap)).build() + } else null + } else null + } catch (e: Exception) { + null + } ?: CarIcon.Builder(IconCompat.createWithResource(carContext, R.mipmap.ic_launcher)).build() + + builder.addItem( + Row.Builder() + .setTitle(item.name) + .setImage(image, Row.IMAGE_TYPE_LARGE) + .setOnClickListener { + val type = item.type + if (type == com.lagradost.cloudstream3.TvType.TvSeries || + type == com.lagradost.cloudstream3.TvType.Anime || + type == com.lagradost.cloudstream3.TvType.Cartoon || + type == com.lagradost.cloudstream3.TvType.OVA || + type == com.lagradost.cloudstream3.TvType.AsianDrama || + type == com.lagradost.cloudstream3.TvType.Documentary) { + screenManager.push(TvSeriesDetailScreen(carContext, item)) + } else { + screenManager.push(DetailsScreen(carContext, item)) + } + } + .build() + ) + } + + itemList = builder.build() + invalidate() + } + is Resource.Failure -> { + // Handle error + } + else -> {} + } + } + } + + override fun onGetTemplate(): Template { + return SearchTemplate.Builder(searchCallback) + .setSearchHint("${CarStrings.get(R.string.car_search)}...") + .setHeaderAction(Action.BACK) + .setShowKeyboardByDefault(true) + .setItemList(itemList ?: ItemList.Builder().build()) + .build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/SourceSelectionScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/SourceSelectionScreen.kt new file mode 100644 index 00000000000..ba49f0f6e4a --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/SourceSelectionScreen.kt @@ -0,0 +1,132 @@ +package com.lagradost.cloudstream3.ui.car + +import android.graphics.Color +import androidx.car.app.CarContext +import androidx.car.app.CarToast +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.CarIcon +import androidx.car.app.model.ItemList +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import androidx.core.graphics.drawable.IconCompat +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Screen that displays available streaming sources for a content item. + * User can select a source, which will be used for playback. + * + * @param apiName The API/provider name to use for loading links + * @param dataUrl The data URL to load links for (movie URL or episode data) + * @param currentSourceUrl The currently selected source URL (for showing check mark) + * @param onSourceSelected Callback when a source is selected + */ +class SourceSelectionScreen( + carContext: CarContext, + private val apiName: String, + private val dataUrl: String, + private val currentSourceUrl: String? = null, + private val onSourceSelected: (ExtractorLink) -> Unit +) : Screen(carContext) { + + private val scope = CoroutineScope(Dispatchers.IO + Job()) + private var isLoading = true + private var sources: List = emptyList() + private var errorMessage: String? = null + + init { + loadSources() + } + + private fun loadSources() { + scope.launch { + try { + val api = getApiFromNameNull(apiName) + if (api == null) { + errorMessage = CarStrings.get(R.string.car_provider_not_found) + isLoading = false + invalidate() + return@launch + } + + val links = mutableListOf() + api.loadLinks(dataUrl, false, {}, { link -> + links.add(link) + }) + + withContext(Dispatchers.Main) { + if (links.isEmpty()) { + errorMessage = CarStrings.get(R.string.car_no_source_found) + } else { + // Sort by quality (highest first) + sources = links.sortedByDescending { it.quality } + } + isLoading = false + invalidate() + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + errorMessage = "${CarStrings.get(R.string.car_error)}: ${e.message}" + isLoading = false + invalidate() + } + } + } + } + + override fun onGetTemplate(): Template { + val templateBuilder = ListTemplate.Builder() + .setTitle(CarStrings.get(R.string.car_sources)) + .setHeaderAction(Action.BACK) + + if (isLoading) { + // When loading, just set loading state without any list + templateBuilder.setLoading(true) + } else { + // Not loading - build the list + val listBuilder = ItemList.Builder() + + if (errorMessage != null) { + listBuilder.setNoItemsMessage(errorMessage!!) + } else if (sources.isEmpty()) { + listBuilder.setNoItemsMessage(CarStrings.get(R.string.car_no_source_available)) + } else { + // Add each source as a row + for (source in sources) { + val qualityStr = Qualities.getStringByInt(source.quality) + val title = if (qualityStr.isNotEmpty()) { + "${source.name} - $qualityStr" + } else { + source.name + } + + val rowBuilder = Row.Builder() + .setTitle(title) + .addText(source.source) + .setOnClickListener { + onSourceSelected(source) + CarToast.makeText(carContext, "${CarStrings.get(R.string.car_source_selected)}: ${source.name}", CarToast.LENGTH_SHORT).show() + screenManager.pop() + } + + + listBuilder.addItem(rowBuilder.build()) + } + } + + templateBuilder.setSingleList(listBuilder.build()) + templateBuilder.setLoading(false) + } + + return templateBuilder.build() + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/car/TvSeriesDetailScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/car/TvSeriesDetailScreen.kt new file mode 100644 index 00000000000..317d9b5c606 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/car/TvSeriesDetailScreen.kt @@ -0,0 +1,222 @@ +package com.lagradost.cloudstream3.ui.car + +import com.lagradost.cloudstream3.utils.CarHelper + +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.CarColor +import androidx.car.app.model.CarIcon +import androidx.car.app.model.ForegroundCarColorSpan +import androidx.car.app.model.Pane +import androidx.car.app.model.PaneTemplate +import androidx.car.app.model.Row +import androidx.car.app.model.Template +import androidx.core.graphics.drawable.IconCompat +import androidx.core.graphics.drawable.toBitmap +import coil3.asDrawable +import coil3.request.ImageRequest +import coil3.SingletonImageLoader +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.TvSeriesLoadResponse +import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.FavoritesData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import android.text.SpannableString +import android.text.Spanned + +class TvSeriesDetailScreen( + carContext: CarContext, + private val item: SearchResponse +) : Screen(carContext) { + + private val scope = CoroutineScope(Dispatchers.IO + Job()) + private var fullDetails: TvSeriesLoadResponse? = null + private var isLoading = true + private var errorMessage: String? = null + private var posterBitmap: android.graphics.Bitmap? = null + private var logoBitmap: android.graphics.Bitmap? = null + private var isFavorite: Boolean = false + + init { + loadDetails() + } + + private fun loadDetails() { + scope.launch { + + + // Load full details + val api = getApiFromNameNull(item.apiName) + if (api != null) { + val repo = APIRepository(api) + when (val result = repo.load(item.url)) { + is Resource.Success -> { + val data = result.value + if (data is TvSeriesLoadResponse) { + fullDetails = data + // Check Favorite status + val id = data.url.replace(api.mainUrl, "").replace("/", "").hashCode() + isFavorite = DataStoreHelper.getFavoritesData(id) != null + + // Load Main Image (Background/Landscape preferred, else Poster) + val bgUrl = data.backgroundPosterUrl + val posterUrl = data.posterUrl ?: item.posterUrl + val targetUrl = bgUrl ?: posterUrl + + if (!targetUrl.isNullOrEmpty()) { + try { + val request = ImageRequest.Builder(carContext) + .data(targetUrl) + .size(1200, 800) + .build() + val imgResult = SingletonImageLoader.get(carContext).execute(request) + posterBitmap = imgResult.image?.asDrawable(carContext.resources)?.toBitmap()?.let { + CarHelper.ensureSoftwareBitmap(it) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + if (!data.logoUrl.isNullOrEmpty()) { + try { + val request = ImageRequest.Builder(carContext) + .data(data.logoUrl) + .size(600, 200) + .build() + val imgResult = SingletonImageLoader.get(carContext).execute(request) + logoBitmap = imgResult.image?.asDrawable(carContext.resources)?.toBitmap()?.let { + CarHelper.ensureSoftwareBitmap(it) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } else { + errorMessage = CarStrings.get(R.string.car_not_tv_series) + } + isLoading = false + } + is Resource.Failure -> { + errorMessage = result.errorString ?: CarStrings.get(R.string.car_error_loading_details) + isLoading = false + } + is Resource.Loading -> {} + } + } else { + errorMessage = CarStrings.get(R.string.car_provider_not_found) + isLoading = false + } + invalidate() + } + } + + private fun toggleFavorite() { + CarHelper.toggleFavorite(carContext, fullDetails, isFavorite) { newStatus -> + isFavorite = newStatus + invalidate() + } + } + + override fun onGetTemplate(): Template { + val paneBuilder = Pane.Builder() + + if (isLoading) { + paneBuilder.setLoading(true) + } else { + val details = fullDetails + + // Include Hero Image if available + // Overlay Strategy: Gradient at bottom + Logo Bottom-Left + // Force Square construction even if logo is null, to ensure we control the aspect ratio + val finalBitmap = if (posterBitmap != null) { + CarHelper.generateSquareImageWithLogo(posterBitmap!!, logoBitmap) + } else { + posterBitmap + } + + finalBitmap?.let { + paneBuilder.setImage(CarIcon.Builder(IconCompat.createWithBitmap(it)).build()) + } ?: run { + paneBuilder.setImage(CarIcon.Builder(IconCompat.createWithResource(carContext, R.mipmap.ic_launcher)).build()) + } + + if (details != null) { + // Meta Row: Year • Score • Duration/Seasons + val metaStringBuilder = StringBuilder() + details.year?.let { metaStringBuilder.append("$it") } + + val score = details.score + if (score != null) { + if (metaStringBuilder.isNotEmpty()) metaStringBuilder.append(" • ") + metaStringBuilder.append(String.format("%.1f/10", score.toDouble(10))) + } + + // Show season count in meta + // details.seasonNames could be used, or just aggregating episodes + val seasonCount = details.episodes.mapNotNull { it.season }.distinct().count() + if (metaStringBuilder.isNotEmpty()) metaStringBuilder.append(" • ") + metaStringBuilder.append("$seasonCount ${CarStrings.get(R.string.car_seasons)}") + + if (metaStringBuilder.isNotEmpty()) { + paneBuilder.addRow( + Row.Builder() + .setTitle(metaStringBuilder.toString()) + .build() + ) + } + + // Favorite Action + val favoriteIconRes = if (isFavorite) R.drawable.ic_baseline_favorite_24 else R.drawable.ic_baseline_favorite_border_24 + val favoriteIcon = IconCompat.createWithResource(carContext, favoriteIconRes) + if (isFavorite) { + favoriteIcon.setTint(0xFFFF0000.toInt()) // Red + } else { + favoriteIcon.setTint(android.graphics.Color.WHITE) // White + } + + val favoriteAction = Action.Builder() + .setIcon(CarIcon.Builder(favoriteIcon).build()) + .setOnClickListener { + toggleFavorite() + } + .build() + + // Episodes Button Action + // "Episode List >" + paneBuilder.addAction( + Action.Builder() + .setTitle(CarStrings.get(R.string.car_episode_list)) + .setBackgroundColor(CarColor.PRIMARY) + .setOnClickListener { + screenManager.push(EpisodeListScreen(carContext, details)) + } + .build() + ) + + paneBuilder.addAction(favoriteAction) + + // Plot and Cast + CarHelper.addPlotAndCast(paneBuilder, details) + + } else if (errorMessage != null) { + paneBuilder.addRow(Row.Builder().setTitle("${CarStrings.get(R.string.car_error)}: $errorMessage").build()) + } + } + + return PaneTemplate.Builder(paneBuilder.build()) + .setTitle(item.name) + .setHeaderAction(Action.BACK) + .build() + } +} 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 49c6d0d7732..6fe20e4e648 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 @@ -847,6 +847,11 @@ class HomeFragment : BaseFragment( return@observeNullable } + // Fix: Prevent WindowLeaked crash if activity is not in a valid state (e.g. when using Android Auto) + if (activity?.isFinishing == true || activity?.isDestroyed == true || !lifecycle.currentState.isAtLeast(androidx.lifecycle.Lifecycle.State.RESUMED)) { + return@observeNullable + } + // don't recreate if (bottomSheetDialog != null) { return@observeNullable diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/CarHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/CarHelper.kt new file mode 100644 index 00000000000..7bd5cfe499f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/CarHelper.kt @@ -0,0 +1,193 @@ +package com.lagradost.cloudstream3.utils + +import com.lagradost.cloudstream3.ui.car.CarStrings + +import androidx.car.app.CarContext +import androidx.car.app.model.CarColor +import androidx.car.app.model.ForegroundCarColorSpan +import androidx.car.app.model.Pane +import androidx.car.app.model.Row +import android.text.SpannableString +import android.text.Spanned +import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.FavoritesData +import com.lagradost.cloudstream3.Episode +import com.lagradost.cloudstream3.TvSeriesLoadResponse +import com.lagradost.cloudstream3.ui.result.getId + +object CarHelper { + + fun toggleFavorite( + carContext: CarContext, + fullDetails: LoadResponse?, + isFavorite: Boolean, + onFavoriteChanged: (Boolean) -> Unit + ) { + val details = fullDetails ?: return + val api = getApiFromNameNull(details.apiName) ?: return + val id = details.url.replace(api.mainUrl, "").replace("/", "").hashCode() + + if (isFavorite) { + DataStoreHelper.removeFavoritesData(id) + onFavoriteChanged(false) + androidx.car.app.CarToast.makeText(carContext, CarStrings.get(R.string.car_removed_from_favorites), androidx.car.app.CarToast.LENGTH_SHORT).show() + } else { + val favoritesData = FavoritesData( + favoritesTime = System.currentTimeMillis(), + id = id, + latestUpdatedTime = System.currentTimeMillis(), + name = details.name, + url = details.url, + apiName = details.apiName, + type = details.type, + posterUrl = details.posterUrl, + year = details.year, + quality = null, + posterHeaders = details.posterHeaders, + plot = details.plot, + score = details.score, + tags = details.tags + ) + DataStoreHelper.setFavoritesData(id, favoritesData) + onFavoriteChanged(true) + androidx.car.app.CarToast.makeText(carContext, CarStrings.get(R.string.car_added_to_favorites), androidx.car.app.CarToast.LENGTH_SHORT).show() + } + } + + fun addPlotAndCast(paneBuilder: Pane.Builder, details: LoadResponse) { + // Plot Row + if (!details.plot.isNullOrEmpty()) { + paneBuilder.addRow( + Row.Builder() + .setTitle(CarStrings.get(R.string.car_plot)) + .addText(details.plot!!) + .build() + ) + } + + // Cast Row + if (!details.actors.isNullOrEmpty()) { + val castList = details.actors!!.groupBy { it.roleString }.flatMap { it.value }.take(5).joinToString(", ") { it.actor.name } + if (castList.isNotEmpty()) { + val s = SpannableString(castList) + s.setSpan( + ForegroundCarColorSpan.create(CarColor.SECONDARY), + 0, + s.length, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + + paneBuilder.addRow( + Row.Builder() + .setTitle(CarStrings.get(R.string.car_cast)) + .addText(s) + .build() + ) + } + } + } + + fun ensureSoftwareBitmap(bitmap: android.graphics.Bitmap): android.graphics.Bitmap { + return if (bitmap.config == android.graphics.Bitmap.Config.HARDWARE) { + bitmap.copy(android.graphics.Bitmap.Config.ARGB_8888, false) + } else { + bitmap + } + } + + fun generateSquareImageWithLogo(poster: android.graphics.Bitmap, logo: android.graphics.Bitmap?): android.graphics.Bitmap { + return try { + val dimension = minOf(poster.width, poster.height) + val squareBitmap = android.graphics.Bitmap.createBitmap(dimension, dimension, android.graphics.Bitmap.Config.ARGB_8888) + val canvas = android.graphics.Canvas(squareBitmap) + + // 1. Draw Poster (Center Crop) + val sourceRect = if (poster.width > poster.height) { + // Wide image: crop center horizontally + val left = (poster.width - poster.height) / 2 + android.graphics.Rect(left, 0, left + poster.height, poster.height) + } else { + // Tall image: crop center vertically + val top = (poster.height - poster.width) / 2 + android.graphics.Rect(0, top, poster.width, top + poster.width) + } + val destRect = android.graphics.Rect(0, 0, dimension, dimension) + canvas.drawBitmap(poster, sourceRect, destRect, null) + + // 2. Draw Gradient (Bottom Dark Shade) + val gradientPaint = android.graphics.Paint() + val gradient = android.graphics.LinearGradient( + 0f, dimension * 0.4f, // Start transparent higher up + 0f, dimension.toFloat(), + android.graphics.Color.TRANSPARENT, + android.graphics.Color.parseColor("#E6000000"), // 90% Black + android.graphics.Shader.TileMode.CLAMP + ) + gradientPaint.shader = gradient + canvas.drawRect(0f, dimension * 0.4f, dimension.toFloat(), dimension.toFloat(), gradientPaint) + + // 3. Draw Logo (if available) safely at bottom center + if (logo != null) { + val logoWidth = logo.width + val logoHeight = logo.height + + // Max width 60% of square, Max height 25% of square + val maxWidth = dimension * 0.6f + val maxHeight = dimension * 0.25f + + val scaleX = maxWidth / logoWidth + val scaleY = maxHeight / logoHeight + val scale = minOf(scaleX, scaleY, 1.0f) // Don't upscale, only downscale + + val newWidth = (logoWidth * scale).toInt() + val newHeight = (logoHeight * scale).toInt() + + val scaledLogo = android.graphics.Bitmap.createScaledBitmap(logo, newWidth, newHeight, true) + + // Position: Bottom Center with margin + val bottomMargin = dimension * 0.05f // 5% margin + val left = (dimension - scaledLogo.width) / 2f + val top = dimension - scaledLogo.height - bottomMargin + + // --- DROP SHADOW EFFECT --- + // Draw a black, semi-transparent silhouette slightly offset to make the white text pop + val shadowPaint = android.graphics.Paint() + // Tint the bitmap BLACK using SRC_IN (keeps alpha channel but changes color) + shadowPaint.colorFilter = android.graphics.PorterDuffColorFilter(android.graphics.Color.BLACK, android.graphics.PorterDuff.Mode.SRC_IN) + shadowPaint.alpha = 160 // 0-255 transparency (approx 60% opacity) + + val shadowOffset = dimension * 0.005f // Dynamic offset based on size (approx 3-5px) + canvas.drawBitmap(scaledLogo, left + shadowOffset, top + shadowOffset, shadowPaint) + // -------------------------- + + // Draw Original Logo + canvas.drawBitmap(scaledLogo, left, top, null) + } + + squareBitmap + } catch (e: Exception) { + e.printStackTrace() + poster // Fallback to original poster if combined generation fails + } + } + + fun generateConsistentEpisodeId(episode: Episode, response: LoadResponse?): Int? { + val data = response ?: return episode.data.hashCode() + if (data !is TvSeriesLoadResponse) return data.getId() + + val mainId = data.getId() + // We need to match the sorting logic from ResultViewModel2/EpisodeListScreen + // to ensure ID consistency + val allEpisodesSorted = data.episodes.sortedBy { (it.season?.times(10_000) ?: 0) + (it.episode ?: 0) } + val globalIndex = allEpisodesSorted.indexOfFirst { it.data == episode.data } + + if (globalIndex == -1) return episode.data.hashCode() + + val matchedEpisode = allEpisodesSorted[globalIndex] + val episodeIndex = matchedEpisode.episode ?: (globalIndex + 1) + return mainId + (matchedEpisode.season?.times(100_000) ?: 0) + episodeIndex + 1 + } +} 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 217dc2a5205..4dfc5a83dce 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -50,6 +50,8 @@ const val RESULT_SEASON = "result_season" const val RESULT_DUB = "result_dub" const val KEY_RESULT_SORT = "result_sort" const val USER_PINNED_PROVIDERS = "user_pinned_providers" //key for pinned user set +const val CAR_CATEGORY_VIEW_MODE = "car_category_view_mode" +const val CAR_PLAYER_MODE = "car_player_mode" class UserPreferenceDelegate( private val key: String, private val default: T //, private val klass: KClass @@ -147,6 +149,9 @@ object DataStoreHelper { _resultsSortingMode = value.ordinal } + var carCategoryViewMode: Int by UserPreferenceDelegate(CAR_CATEGORY_VIEW_MODE, 0) + var carPlayerMode: Int by UserPreferenceDelegate(CAR_PLAYER_MODE, 0) + data class Account( @JsonProperty("keyIndex") val keyIndex: Int, diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PlayerCarHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PlayerCarHelper.kt new file mode 100644 index 00000000000..5647431d0c8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PlayerCarHelper.kt @@ -0,0 +1,180 @@ +package com.lagradost.cloudstream3.utils + +import android.content.Context +import android.graphics.Bitmap +import android.util.Log +import androidx.media3.common.MediaMetadata +import coil3.SingletonImageLoader +import coil3.request.ImageRequest +import coil3.asDrawable +import androidx.core.graphics.drawable.toBitmap +import com.lagradost.cloudstream3.Episode +import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey +import com.lagradost.cloudstream3.ui.player.NEXT_WATCH_EPISODE_PERCENTAGE +import com.lagradost.cloudstream3.ui.result.VideoWatchState +import com.lagradost.cloudstream3.utils.DataStoreHelper.setVideoWatchState + +object PlayerCarHelper { + private const val TAG = "PlayerCarHelper" + + fun saveHeaderCache( + item: SearchResponse?, + data: LoadResponse?, + currentParentId: Int? + ) { + try { + Log.d(TAG, "saveHeaderCache called. Item: ${item?.url}, Data: ${data?.url}, ParentID: $currentParentId") + + val url = item?.url ?: data?.url ?: return + val apiName = item?.apiName ?: data?.apiName ?: return + val name = item?.name ?: data?.name ?: return + val type = item?.type ?: data?.type ?: TvType.Movie + val poster = item?.posterUrl ?: data?.posterUrl + + val api = getApiFromNameNull(apiName) + val mainUrl = api?.mainUrl ?: "" + val id = url.replace(mainUrl, "").replace("/", "").hashCode() + + val header = VideoDownloadHelper.DownloadHeaderCached( + apiName = apiName, + url = url, + type = type, + name = name, + poster = poster, + id = id, + cacheTime = System.currentTimeMillis() + ) + Log.e(TAG, "Saving Header Cache: ID=$id, ParentID=$currentParentId, Name=${name}, Type=${header.type}") + setKey(DOWNLOAD_HEADER_CACHE, id.toString(), header) + // Ensure parentId is also covered if different + currentParentId?.let { parentId -> + if (parentId != id) { + setKey(DOWNLOAD_HEADER_CACHE, parentId.toString(), header.copy(id = parentId)) + Log.e(TAG, "Saved extra copy for ParentID: $parentId") + } + } + } catch (e: Exception) { + Log.e(TAG, "Error saving header cache", e) + } + } + + fun saveProgress( + pos: Long, + dur: Long, + item: SearchResponse?, + activeEpisode: Episode?, + currentEpisodeId: Int?, + currentParentId: Int? + ) { + Log.d(TAG, "saveProgress - Pos: $pos, Dur: $dur") + + if (item?.type == TvType.Live) { + Log.d(TAG, "Skipping saveProgress for Live content") + return + } + + if (dur <= 0) return + + // 1. Save detailed position (setViewPos) + // Only if we have a valid ID. For Movies, currentEpisodeId might be enough. + // For Episodes, we need the specific episode ID. + if (currentEpisodeId != null) { + DataStoreHelper.setViewPos(currentEpisodeId, pos, dur) + Log.d(TAG, "Called setViewPos for EpisodeID: $currentEpisodeId") + } else { + Log.d(TAG, "Skipping setViewPos (EpisodeID is null)") + } + + // 2. Save "Last Watched" for Continue Watching list + // This requires parentId. + if (currentParentId != null) { + // Logic to check if finished (95% rule) + val percentage = pos * 100L / dur + if (percentage >= NEXT_WATCH_EPISODE_PERCENTAGE) { + // Mark as finished / remove from resume + DataStoreHelper.removeLastWatched(currentParentId) + // Mark as watched + if (currentEpisodeId != null) { + setVideoWatchState(currentEpisodeId, VideoWatchState.Watched) + } + Log.d(TAG, "Marked as Watched & Removed Last Watched (Finished)") + } else { + // Update resume state + val epNum = activeEpisode?.episode + val seasonNum = activeEpisode?.season + + DataStoreHelper.setLastWatched( + parentId = currentParentId, + episodeId = currentEpisodeId, + episode = epNum, + season = seasonNum, + isFromDownload = false, + updateTime = System.currentTimeMillis() + ) + Log.d(TAG, "Set Last Watched for ParentID: $currentParentId") + } + } else { + Log.d(TAG, "Skipping setLastWatched (ParentID is null)") + } + } + + suspend fun createMediaMetadata( + context: Context, + item: SearchResponse?, + activeEpisode: Episode? + ): MediaMetadata = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) { + // Create MediaMetadata + val metadataBuilder = MediaMetadata.Builder() + .setTitle(item?.name ?: activeEpisode?.name ?: "Video") + .setDisplayTitle(item?.name ?: activeEpisode?.name ?: "Video") + .setArtist(activeEpisode?.name ?: item?.apiName ?: "Cloudstream") + + // Try to load artwork + val posterUrl = activeEpisode?.posterUrl ?: item?.posterUrl + if (!posterUrl.isNullOrEmpty()) { + try { + // Use applicationContext to avoid leaks if possible, but CarContext is fine for this scope usually + val request = ImageRequest.Builder(context) + .data(posterUrl) + .size(512, 512) // Optimize size for metadata + .build() + val result = SingletonImageLoader.get(context).execute(request) + val bitmap = result.image?.asDrawable(context.resources)?.toBitmap() + if (bitmap != null) { + metadataBuilder.setArtworkData( + bitmap.let { + val stream = java.io.ByteArrayOutputStream() + it.compress(Bitmap.CompressFormat.PNG, 100, stream) + stream.toByteArray() + }, + MediaMetadata.PICTURE_TYPE_FRONT_COVER + ) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to load artwork for metadata", e) + } + } + metadataBuilder.build() + } + + fun formatDuration(millis: Long): String { + if (millis < 0) return "00:00" + val totalSeconds = millis / 1000 + val seconds = totalSeconds % 60 + val minutes = (totalSeconds / 60) % 60 + val hours = totalSeconds / 3600 + + return if (hours > 0) { + "%d:%02d:%02d".format(hours, minutes, seconds) + } else { + "%02d:%02d".format(minutes, seconds) + } + } +} diff --git a/app/src/main/res-car/values-b+af/strings_car.xml b/app/src/main/res-car/values-b+af/strings_car.xml new file mode 100644 index 00000000000..0c22f6d3416 --- /dev/null +++ b/app/src/main/res-car/values-b+af/strings_car.xml @@ -0,0 +1,54 @@ + + + Laai tans... + Fout + Fout met laai van inhoud + Gunstelinge + Geskiedenis + Aflaaie + Verskaffer + Huidige + Kieslys + Tuisinhoud + Geen inhoud van verskaffer + Verskaffer nie gevind nie + Bron + Bronne + Storielyn + Rolverdeling + Fout met laai van besonderhede + Episode lys + Seisoen + Seisoene + Episode + Geen episodes gevind nie + Nie \'n TV-reeks nie + By gunstelinge gevoeg + Van gunstelinge verwyder + Geen gunstelinge gevind nie + Begin terugspeel... + Speel tans + Geen skakel gevind nie + Geen speelbare inhoud gevind nie + Kan nie besonderhede laai nie + Fout met laai van skakels + Pas by skerm + Vul skerm + Geen bron gevind nie + Geen bron beskikbaar nie + Bron gekies + Soek + Geen resultate gevind nie + \'Gaan voort om te kyk\' nie gevind nie + Geen aflaaie gevind nie + Lêer nie gevind nie + Geen geldige episode gevind nie + Kies Verskaffer + Gekies + Biblioteek + Boekmerke + Hervat %1$s... + Spelermodus + Gevorderde Speler (hardware) + Eenvoudige Speler (legacy) + diff --git a/app/src/main/res-car/values-b+am/strings_car.xml b/app/src/main/res-car/values-b+am/strings_car.xml new file mode 100644 index 00000000000..1dc05795f5b --- /dev/null +++ b/app/src/main/res-car/values-b+am/strings_car.xml @@ -0,0 +1,54 @@ + + + በመጫን ላይ... + ስህተት + ይዘት በመጫን ላይ ስህተት + ተወዳጆች + ታሪክ + ውርዶች + አቅራቢ + የአሁኑ + ምናሌ + የመነሻ ይዘት + ከአቅራቢው ምንም ይዘት የለም + አቅራቢ አልተገኘም + ምንጭ + ምንጮች + ሴራ + ተዋንያን + ዝርዝሮችን በመጫን ላይ ስህተት + የክፍሎች ዝርዝር + ወቅት + ወቅቶች + ክፍል + ምንም ክፍሎች አልተገኙም + የቲቪ ተከታታይ አይደለም + ወደ ተወዳጆች ታክሏል + ከተወዳጆች ተወግዷል + ምንም ተወዳጆች አልተገኙም + ማጫወት በመጀመር ላይ... + በማጫወት ላይ + ምንም አገናኝ አልተገኘም + ምንም የሚጫወት ይዘት አልተገኘም + ዝርዝሮችን መጫን አልተቻለም + አገናኞችን በመጫን ላይ ስህተት + ስክሪን ላይ ይግጠም + ስክሪን ይሙሉ + ምንም ምንጭ አልተገኘም + ምንም ምንጭ የለም + ምንጭ ተመርጧል + ፈልግ + ምንም ውጤቶች አልተገኙም + \'መመልከት ይቀጥሉ\' አልተገኘም + ምንም ውርዶች አልተገኙም + ፋይል አልተገኘም + ምንም ትክክለኛ ክፍል አልተገኘም + አቅራቢ ይምረጡ + ተመርጧል + ቤተ-መጽሐፍት + ዕልባቶች + %1$s በመቀጠል ላይ... + የማጫወቻ ሁነታ + የላቀ ማጫወቻ (hardware) + ቀላል ማጫወቻ (legacy) + diff --git a/app/src/main/res-car/values-b+apc/strings_car.xml b/app/src/main/res-car/values-b+apc/strings_car.xml new file mode 100644 index 00000000000..73c59d8be3c --- /dev/null +++ b/app/src/main/res-car/values-b+apc/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + وضع المشغل + مشغل متقدم (hardware) + مشغل بسيط (legacy) + diff --git a/app/src/main/res-car/values-b+ar/strings_car.xml b/app/src/main/res-car/values-b+ar/strings_car.xml new file mode 100644 index 00000000000..c17ef0d4a2a --- /dev/null +++ b/app/src/main/res-car/values-b+ar/strings_car.xml @@ -0,0 +1,54 @@ + + + جارٍ التحميل... + خطأ + خطأ في تحميل المحتوى + المفضلة + السجل + التنزيلات + المزود + الحالي + القائمة + محتوى الصفحة الرئيسية + لا يوجد محتوى من المزود + المزود غير موجود + المصدر + المصادر + القصة + الممثلون + خطأ في تحميل التفاصيل + قائمة الحلقات + الموسم + المواسم + الحلقة + لم يتم العثور على حلقات + ليس مسلسلاً تلفزيونياً + تمت الإضافة إلى المفضلة + تمت الإزالة من المفضلة + لم يتم العثور على مفضلة + بدء التشغيل... + تشغيل + لم يتم العثور على رابط + لم يتم العثور على محتوى قابل للتشغيل + تعذر تحميل التفاصيل + خطأ في تحميل الروابط + ملاءمة للشاشة + ملء الشاشة + لم يتم العثور على مصدر + لا يوجد مصدر متاح + تم اختيار المصدر + بحث + لم يتم العثور على نتائج + لم يتم العثور على \'متابعة المشاهدة\' + لم يتم العثور على تنزيلات + الملف غير موجود + لا توجد حلقة صالحة + اختر المزود + محدد + المكتبة + الإشارات المرجعية + استئناف %1$s... + وضع المشغل + مشغل متقدم (hardware) + مشغل بسيط (legacy) + diff --git a/app/src/main/res-car/values-b+ars/strings_car.xml b/app/src/main/res-car/values-b+ars/strings_car.xml new file mode 100644 index 00000000000..73c59d8be3c --- /dev/null +++ b/app/src/main/res-car/values-b+ars/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + وضع المشغل + مشغل متقدم (hardware) + مشغل بسيط (legacy) + diff --git a/app/src/main/res-car/values-b+as/strings_car.xml b/app/src/main/res-car/values-b+as/strings_car.xml new file mode 100644 index 00000000000..a28618eaefa --- /dev/null +++ b/app/src/main/res-car/values-b+as/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + প্লেয়াৰ মোড + উন্নত প্লেয়াৰ (hardware) + সৰল প্লেয়াৰ (legacy) + diff --git a/app/src/main/res-car/values-b+az/strings_car.xml b/app/src/main/res-car/values-b+az/strings_car.xml new file mode 100644 index 00000000000..8a9cc77eb8c --- /dev/null +++ b/app/src/main/res-car/values-b+az/strings_car.xml @@ -0,0 +1,54 @@ + + + Yüklənir... + Xəta + Məzmun yüklənərkən xəta + Sevimlilər + Tarixçə + Yükləmələr + Provayder + Cari + Menyu + Ana səhifə məzmunu + Provayderdən məzmun yoxdur + Provayder tapılmadı + Mənbə + Mənbələr + Süjet + Rollarda + Təfərrüatları yükləyərkən xəta + Epizod siyahısı + Mövsüm + Mövsümlər + Epizod + Epizod tapılmadı + TV serialı deyil + Sevimlilərə əlavə edildi + Sevimlilərdən silindi + Sevimli tapılmadı + Oxutma başlayır... + Oxudulur + Link tapılmadı + Oxudula bilən məzmun tapılmadı + Təfərrüatları yükləmək mümkün olmadı + Linkləri yükləyərkən xəta + Ekranı uyğunlaşdır + Ekranı doldur + Mənbə tapılmadı + Mövcud mənbə yoxdur + Mənbə seçildi + Axtarış + Nəticə tapılmadı + \'İzləməyə davam et\' tapılmadı + Yükləmə tapılmadı + Fayl tapılmadı + Etibarlı epizod tapılmadı + Provayder seç + Seçildi + Kitabxana + Əlfəcinlər + %1$s davam etdirilir... + Pleyer rejimi + Təkmilləşdirilmiş Pleyer (hardware) + Sadə Pleyer (legacy) + diff --git a/app/src/main/res-car/values-b+be/strings_car.xml b/app/src/main/res-car/values-b+be/strings_car.xml new file mode 100644 index 00000000000..cdc6e88c8d4 --- /dev/null +++ b/app/src/main/res-car/values-b+be/strings_car.xml @@ -0,0 +1,54 @@ + + + Загрузка... + Памылка + Памылка загрузкі кантэнту + Выбранае + Гісторыя + Спампоўкі + Пастаўшчык + Бягучы + Меню + Галоўны кантэнт + Няма кантэнту ад пастаўшчыка + Пастаўшчык не знойдзены + Крыніца + Крыніцы + Сюжэт + Акцёры + Памылка загрузкі дэталяў + Спіс серый + Сезон + Сезоны + Серыя + Серый не знойдзена + Не серыял + Дададзена ў выбранае + Выдалена з выбранага + Няма выбранага + Пачатак прайгравання... + Прайграванне + Спасылка не знойдзена + Няма кантэнту для прайгравання + Не ўдалося загрузіць дэталі + Памылка загрузкі спасылак + Па памеры экрана + Запоўніць экран + Крыніца не знойдзена + Няма даступных крыніц + Крыніца выбрана + Пошук + Вынікаў не знойдзена + \'Працягнуць прагляд\' не знойдзена + Няма спамповак + Файл не знойдзены + Няма сапраўднай серыі + Выберыце пастаўшчыка + Выбрана + Бібліятэка + Закладкі + Аднаўленне %1$s... + Рэжым прайгравальніка + Пашыраны прайгравальнік (hardware) + Просты прайгравальнік (legacy) + diff --git a/app/src/main/res-car/values-b+bg/strings_car.xml b/app/src/main/res-car/values-b+bg/strings_car.xml new file mode 100644 index 00000000000..0d53639346e --- /dev/null +++ b/app/src/main/res-car/values-b+bg/strings_car.xml @@ -0,0 +1,54 @@ + + + Зареждане... + Грешка + Грешка при зареждане на съдържание + Любими + История + Изтегляния + Доставчик + Текущ + Меню + Начално съдържание + Няма съдържание от доставчика + Доставчикът не е намерен + Източник + Източници + Сюжет + Актьорски състав + Грешка при зареждане на детайли + Списък с епизоди + Сезон + Сезони + Епизод + Няма намерени епизоди + Не е ТВ сериал + Добавено към любими + Премахнато от любими + Няма намерени любими + Стартиране на възпроизвеждане... + Възпроизвеждане + Няма намерена връзка + Няма намерено съдържание за възпроизвеждане + Не може да се заредят детайли + Грешка при зареждане на връзки + Побиране на екрана + Запълване на екрана + Няма намерен източник + Няма наличен източник + Избран източник + Търсене + Няма намерени резултати + Няма намерено \'Продължи гледането\' + Няма намерени изтегляния + Файлът не е намерен + Няма намерен валиден епизод + Избор на доставчик + Избрано + Библиотека + Отметки + Възобновяване %1$s... + Режим на плейъра + Разширен плейър (hardware) + Прост плейър (legacy) + diff --git a/app/src/main/res-car/values-b+bn/strings_car.xml b/app/src/main/res-car/values-b+bn/strings_car.xml new file mode 100644 index 00000000000..d2a84f6e770 --- /dev/null +++ b/app/src/main/res-car/values-b+bn/strings_car.xml @@ -0,0 +1,54 @@ + + + লোড হচ্ছে... + ত্রুটি + কন্টেন্ট লোড করতে ত্রুটি + পছন্দ + ইতিহাস + ডাউনলোড + প্রদানকারী + বর্তমান + মেনু + হোম কন্টেন্ট + প্রদানকারী থেকে কোন কন্টেন্ট নেই + প্রদানকারী পাওয়া যায়নি + উৎস + উৎসসমূহ + কাহিনী + কুশীলব + বিস্তারিত লোড করতে ত্রুটি + পর্বের তালিকা + সিজন + সিজনসমূহ + পর্ব + কোন পর্ব পাওয়া যায়নি + টিভি সিরিজ নয় + পছন্দে যোগ করা হয়েছে + পছন্দ থেকে সরানো হয়েছে + কোন পছন্দ পাওয়া যায়নি + প্লেব্যাক শুরু হচ্ছে... + চলছে + কোন লিঙ্ক পাওয়া যায়নি + কোন চালানোর যোগ্য কন্টেন্ট পাওয়া যায়নি + বিস্তারিত লোড করা যায়নি + লিঙ্ক লোড করতে ত্রুটি + স্ক্রিনে মানানসই করুন + স্ক্রিন পূর্ণ করুন + কোন উৎস পাওয়া যায়নি + কোন উৎস উপলব্ধ নেই + উৎস নির্বাচিত + অনুসন্ধান + কোন ফলাফল পাওয়া যায়নি + \'দেখা চালিয়ে যান\' পাওয়া যায়নি + কোন ডাউনলোড পাওয়া যায়নি + ফাইল পাওয়া যায়নি + কোন বৈধ পর্ব পাওয়া যায়নি + প্রদানকারী নির্বাচন করুন + নির্বাচিত + লাইব্রেরি + বুকমার্ক + %1$s পুনরায় শুরু হচ্ছে... + প্লেয়ার মোড + উন্নত প্লেয়ার (hardware) + সরল প্লেয়ার (legacy) + diff --git a/app/src/main/res-car/values-b+bs/strings_car.xml b/app/src/main/res-car/values-b+bs/strings_car.xml new file mode 100644 index 00000000000..d952e50da2b --- /dev/null +++ b/app/src/main/res-car/values-b+bs/strings_car.xml @@ -0,0 +1,54 @@ + + + Učitavanje... + Greška + Greška pri učitavanju sadržaja + Omiljeni + Historija + Preuzimanja + Provajder + Trenutno + Meni + Početni sadržaj + Nema sadržaja od provajdera + Provajder nije pronađen + Izvor + Izvori + Radnja + Uloge + Greška pri učitavanju detalja + Lista epizoda + Sezona + Sezone + Epizoda + Nema pronađenih epizoda + Nije TV serija + Dodano u omiljene + Uklonjeno iz omiljenih + Nema omiljenih + Pokretanje reprodukcije... + Reprodukcija + Link nije pronađen + Sadržaj za reprodukciju nije pronađen + Nije moguće učitati detalje + Greška pri učitavanju linkova + Prilagodi ekranu + Popuni ekran + Izvor nije pronađen + Izvor nije dostupan + Izvor odabran + Pretraga + Nema rezultata + \'Nastavi gledanje\' nije pronađeno + Nema preuzimanja + Datoteka nije pronađena + Nema važeće epizode + Izaberi provajdera + Odabrano + Biblioteka + Obeleživači + Nastavljanje %1$s... + Režim reproduktora + Napredni reproduktor (hardware) + Jednostavni reproduktor (legacy) + diff --git a/app/src/main/res-car/values-b+ca/strings_car.xml b/app/src/main/res-car/values-b+ca/strings_car.xml new file mode 100644 index 00000000000..ef5afa88cee --- /dev/null +++ b/app/src/main/res-car/values-b+ca/strings_car.xml @@ -0,0 +1,54 @@ + + + Carregant... + Error + Error carregant contingut + Preferits + Historial + Descàrregues + Proveïdor + Actual + Menú + Contingut d\'inici + Sense contingut del proveïdor + Proveïdor no trobat + Font + Fonts + Argument + Repartiment + Error carregant detalls + Llista d\'episodis + Temporada + Temporades + Episodi + No s\'han trobat episodis + No és una sèrie de TV + Afegit a preferits + Eliminat de preferits + Cap preferit trobat + Iniciant reproducció... + Reproduint + Enllaç no trobat + Contingut reproduïble no trobat + No es poden carregar els detalls + Error carregant enllaços + Ajustar a la pantalla + Omplir la pantalla + Font no trobada + Cap font disponible + Font seleccionada + Cercar + Sense resultats + \'Continuar mirant\' no trobat + Sense descàrregues + Fitxer no trobat + Cap episodi vàlid + Seleccionar proveïdor + Seleccionat + Biblioteca + Marcadors + Reprenent %1$s... + Mode del reproductor + Reproductor avançat (hardware) + Reproductor simple (legacy) + diff --git a/app/src/main/res-car/values-b+ckb/strings_car.xml b/app/src/main/res-car/values-b+ckb/strings_car.xml new file mode 100644 index 00000000000..c77f3f2a2d0 --- /dev/null +++ b/app/src/main/res-car/values-b+ckb/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + دۆخی لێدەر + لێدەری پێشکەوتوو (hardware) + لێدەری سادە (legacy) + diff --git a/app/src/main/res-car/values-b+cs/strings_car.xml b/app/src/main/res-car/values-b+cs/strings_car.xml new file mode 100644 index 00000000000..c523094eb96 --- /dev/null +++ b/app/src/main/res-car/values-b+cs/strings_car.xml @@ -0,0 +1,54 @@ + + + Načítání... + Chyba + Chyba při načítání obsahu + Oblíbené + Historie + Stažené + Poskytovatel + Aktuální + Menu + Domovský obsah + Žádný obsah od poskytovatele + Poskytovatel nenalezen + Zdroj + Zdroje + Děj + Obsazení + Chyba při načítání podrobností + Seznam epizod + Série + Série + Epizoda + Nenalezeny žádné epizody + Není TV seriál + Přidáno do oblíbených + Odebráno z oblíbených + Nenalezeny žádné oblíbené + Spouštění přehrávání... + Přehrávání + Odkaz nenalezen + Nenalezen žádný přehrávatelný obsah + Nelze načíst podrobnosti + Chyba při načítání odkazů + Přizpůsobit obrazovce + Vyplnit obrazovku + Zdroj nenalezen + Žádný zdroj k dispozici + Zdroj vybrán + Hledat + Nenalezeny žádné výsledky + Nenalezeno \'Pokračovat ve sledování\' + Nenalezeny žádné stažené soubory + Soubor nenalezen + Nenalezena žádná platná epizoda + Vybrat poskytovatele + Vybráno + Knihovna + Záložky + Obnovování %1$s... + Režim přehrávače + Pokročilý přehrávač (hardware) + Jednoduchý přehrávač (legacy) + diff --git a/app/src/main/res-car/values-b+da/strings_car.xml b/app/src/main/res-car/values-b+da/strings_car.xml new file mode 100644 index 00000000000..4a07a36aefe --- /dev/null +++ b/app/src/main/res-car/values-b+da/strings_car.xml @@ -0,0 +1,54 @@ + + + Indlæser... + Fejl + Fejl ved indlæsning af indhold + Favoritter + Historik + Downloads + Udbydere + Nuværende + Menu + Hjemmeindhold + Intet indhold fra udbyder + Udbyder ikke fundet + Kilde + Kilder + Handling + Medvirkende + Fejl ved indlæsning af detaljer + Afsnitsliste + Sæson + Sæsoner + Afsnit + Ingen afsnit fundet + Ikke en TV-serie + Tilføjet til favoritter + Fjernet fra favoritter + Ingen favoritter fundet + Starter afspilning... + Afspiller + Intet link fundet + Intet afspilleligt indhold fundet + Kunne ikke indlæse detaljer + Fejl ved indlæsning af links + Tilpas til skærm + Fyld skærm + Ingen kilde fundet + Ingen kilde tilgængelig + Kilde valgt + Søg + Ingen resultater fundet + \'Fortsæt med at se\' ikke fundet + Ingen downloads fundet + Fil ikke fundet + Intet gyldigt afsnit fundet + Vælg udbyder + Valgt + Bibliotek + Bogmærker + Genoptager %1$s... + Afspillertilstand + Avanceret afspiller (hardware) + Simpel afspiller (legacy) + diff --git a/app/src/main/res-car/values-b+de/strings_car.xml b/app/src/main/res-car/values-b+de/strings_car.xml new file mode 100644 index 00000000000..25123d22a78 --- /dev/null +++ b/app/src/main/res-car/values-b+de/strings_car.xml @@ -0,0 +1,54 @@ + + + Laden... + Fehler + Fehler beim Laden des Inhalts + Favoriten + Verlauf + Downloads + Anbieter + Aktuell + Menü + Startseiteninhalt + Kein Inhalt vom Anbieter + Anbieter nicht gefunden + Quelle + Quellen + Handlung + Besetzung + Fehler beim Laden der Details + Episodenliste + Staffel + Staffeln + Episode + Keine Episoden gefunden + Keine TV-Serie + Zu Favoriten hinzugefügt + Aus Favoriten entfernt + Keine Favoriten gefunden + Wiedergabe wird gestartet... + Wiedergabe + Kein Link gefunden + Kein abspielbarer Inhalt gefunden + Details konnten nicht geladen werden + Fehler beim Laden der Links + An Bildschirm anpassen + Bildschirm füllen + Keine Quelle gefunden + Keine Quelle verfügbar + Quelle ausgewählt + Suchen + Keine Ergebnisse gefunden + Keine \'Weiterschauen\'-Elemente gefunden + Keine Downloads gefunden + Datei nicht gefunden + Keine gültige Episode gefunden + Anbieter auswählen + Ausgewählt + Bibliothek + Lesezeichen + Setze %1$s fort... + Player-Modus + Erweiterter Player (Hardware) + Einfacher Player (Legacy) + diff --git a/app/src/main/res-car/values-b+el/strings_car.xml b/app/src/main/res-car/values-b+el/strings_car.xml new file mode 100644 index 00000000000..97a1c68f494 --- /dev/null +++ b/app/src/main/res-car/values-b+el/strings_car.xml @@ -0,0 +1,54 @@ + + + Φόρτωση... + Σφάλμα + Σφάλμα φόρτωσης περιεχομένου + Αγαπημένα + Ιστορικό + Λήψεις + Πάροχος + Τρέχον + Μενού + Περιεχόμενο αρχικής σελίδας + Δεν υπάρχει περιεχόμενο από τον πάροχο + Ο πάροχος δεν βρέθηκε + Πηγή + Πηγές + Πλοκή + Ηθοποιοί + Σφάλμα φόρτωσης λεπτομερειών + Λίστα επεισοδίων + Σεζόν + Σεζόν + Επεισόδιο + Δεν βρέθηκαν επεισόδια + Δεν είναι τηλεοπτική σειρά + Προστέθηκε στα αγαπημένα + Αφαιρέθηκε από τα αγαπημένα + Δεν βρέθηκαν αγαπημένα + Έναρξη αναπαραγωγής... + Αναπαραγωγή + Δεν βρέθηκε σύνδεσμος + Δεν βρέθηκε περιεχόμενο για αναπαραγωγή + Αδυναμία φόρτωσης λεπτομερειών + Σφάλμα φόρτωσης συνδέσμων + Προσαρμογή στην οθόνη + Γέμισμα οθόνης + Δεν βρέθηκε πηγή + Καμία διαθέσιμη πηγή + Επιλέχθηκε πηγή + Αναζήτηση + Δεν βρέθηκαν αποτελέσματα + Δεν βρέθηκε \'Συνέχεια παρακολούθησης\' + Δεν βρέθηκαν λήψεις + Το αρχείο δεν βρέθηκε + Δεν βρέθηκε έγκυρο επεισόδιο + Επιλογή παρόχου + Επιλεγμένο + Βιβλιοθήκη + Σελιδοδείκτες + Συνέχιση %1$s... + Λειτουργία αναπαραγωγής + Προηγμένη αναπαραγωγή (hardware) + Απλή αναπαραγωγή (legacy) + diff --git a/app/src/main/res-car/values-b+eo/strings_car.xml b/app/src/main/res-car/values-b+eo/strings_car.xml new file mode 100644 index 00000000000..444eea01f98 --- /dev/null +++ b/app/src/main/res-car/values-b+eo/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + Ludila reĝimo + Altnivela ludilo (hardware) + Simpla ludilo (legacy) + diff --git a/app/src/main/res-car/values-b+es/strings_car.xml b/app/src/main/res-car/values-b+es/strings_car.xml new file mode 100644 index 00000000000..9eed50f8b03 --- /dev/null +++ b/app/src/main/res-car/values-b+es/strings_car.xml @@ -0,0 +1,54 @@ + + + Cargando... + Error + Error al cargar contenido + Favoritos + Historial + Descargas + Proveedor + Actual + Menú + Contenido de inicio + Sin contenido del proveedor + Proveedor no encontrado + Fuente + Fuentes + Trama + Reparto + Error al cargar detalles + Lista de episodios + Temporada + Temporadas + Episodio + No se encontraron episodios + No es una serie de TV + Añadido a favoritos + Eliminado de favoritos + No se encontraron favoritos + Iniciando reproducción... + Reproduciendo + No se encontró enlace + No se encontró contenido reproducible + No se pudieron cargar los detalles + Error al cargar enlaces + Ajustar a la pantalla + Llenar pantalla + No se encontró fuente + Ninguna fuente disponible + Fuente seleccionada + Buscar + No se encontraron resultados + No se encontró \'Continuar viendo\' + No se encontraron descargas + Archivo no encontrado + No se encontró episodio válido + Seleccionar proveedor + Seleccionado + Biblioteca + Marcadores + Reanudando %1$s... + Modo del reproductor + Reproductor avanzado (hardware) + Reproductor simple (legacy) + diff --git a/app/src/main/res-car/values-b+et/strings_car.xml b/app/src/main/res-car/values-b+et/strings_car.xml new file mode 100644 index 00000000000..6f7b7f8b6cf --- /dev/null +++ b/app/src/main/res-car/values-b+et/strings_car.xml @@ -0,0 +1,54 @@ + + + Laadimine... + Viga + Sisu laadimise viga + Lemmikud + Ajalugu + Allalaadimised + Teenusepakkuja + Praegune + Menüü + Avalehe sisu + Teenusepakkujalt puudub sisu + Teenusepakkujat ei leitud + Allikas + Allikad + Sisu + Osades + Viga üksikasjade laadimisel + Episoodide loend + Hooaeg + Hooajad + Episood + Episoode ei leitud + Ei ole telesari + Lisatud lemmikutesse + Eemaldatud lemmikutest + Lemmikuid ei leitud + Taasesituse alustamine... + Esitamine + Link ei leitud + Mängitavat sisu ei leitud + Üksikasju ei saa laadida + Viga linkide laadimisel + Mahuta ekraanile + Täida ekraan + Allikat ei leitud + Ühtegi allikat pole saadaval + Allikas valitud + Otsi + Tulemusi ei leitud + \'Jätka vaatamist\' ei leitud + Allalaadimisi ei leitud + Faili ei leitud + Kehtivat episoodi ei leitud + Vali teenusepakkuja + Valitud + Raamatukogu + Järjehoidjad + Jätkamine %1$s... + Mängija režiim + Täiustatud mängija (hardware) + Lihtne mängija (legacy) + diff --git a/app/src/main/res-car/values-b+eu/strings_car.xml b/app/src/main/res-car/values-b+eu/strings_car.xml new file mode 100644 index 00000000000..1f3f561051c --- /dev/null +++ b/app/src/main/res-car/values-b+eu/strings_car.xml @@ -0,0 +1,54 @@ + + + Kargatzen... + Errorea + Errorea edukia kargatzean + Gogokoak + Historia + Deskargak + Hornitzailea + Unekoa + Menua + Hasierako edukia + Hornitzailearen edukirik ez + Hornitzailea ez da aurkitu + Iturburua + Iturburuak + Argumentua + Aktoreak + Errorea xehetasunak kargatzean + Atalen zerrenda + Denboraldia + Denboraldiak + Atala + Ez da atalik aurkitu + Ez da telebista saioa + Gogokoetan gehitu da + Gogokoetatik kendu da + Ez da gogokorik aurkitu + Erreprodukzioa hasten... + Erreproduzitzen + Ez da estekarik aurkitu + Ez da erreproduzitzeko edukirik aurkitu + Ezin dira xehetasunak kargatu + Errorea estekak kargatzean + Doitu pantailara + Bete pantaila + Ez da iturbururik aurkitu + Ez dago iturbururik eskuragarri + Iturburua hautatuta + Bilatu + Ez da emaitzarik aurkitu + Ez da \'Jarraitu ikusten\' aurkitu + Ez da deskargarik aurkitu + Ez da fitxategia aurkitu + Ez da baliozko atalik aurkitu + Hautatu hornitzailea + Hautatuta + Liburutegia + Laster-markak + %1$s jarraitzen... + Erreproduzigailu modua + Erreproduzigailu aurreratua (hardware) + Erreproduzigailu sinplea (legacy) + diff --git a/app/src/main/res-car/values-b+fa/strings_car.xml b/app/src/main/res-car/values-b+fa/strings_car.xml new file mode 100644 index 00000000000..b074cbdb537 --- /dev/null +++ b/app/src/main/res-car/values-b+fa/strings_car.xml @@ -0,0 +1,54 @@ + + + درحال بارگذاری... + خطا + خطا در بارگذاری محتوا + علاقه‌مندی‌ها + تاریخچه + دانلودها + ارائه‌دهنده + کنونی + منو + محتوای خانه + بدون محتوا از ارائه‌دهنده + ارائه‌دهنده یافت نشد + منبع + منابع + داستان + بازیگران + خطا در بارگذاری جزئیات + لیست قسمت‌ها + فصل + فصل‌ها + قسمت + قسمتی یافت نشد + سریال تلویزیونی نیست + به علاقه‌مندی‌ها اضافه شد + از علاقه‌مندی‌ها حذف شد + علاقه‌مندی یافت نشد + شروع پخش... + درحال پخش + لینکی یافت نشد + محتوای قابل پخش یافت نشد + ناتوان در بارگذاری جزئیات + خطا در بارگذاری لینک‌ها + فیت صفحه + پر کردن صفحه + منبع یافت نشد + منبعی در دسترس نیست + منبع انتخاب شد + جستجو + نتیجه‌ای یافت نشد + \'ادامه تماشا\' یافت نشد + دانلودی یافت نشد + فایل یافت نشد + قسمت معتبری یافت نشد + انتخاب ارائه‌دهنده + انتخاب شد + کتابخانه + نشانک‌ها + درحال ادامه %1$s... + حالت پخش‌کننده + پخش‌کننده پیشرفته (hardware) + پخش‌کننده ساده (legacy) + diff --git a/app/src/main/res-car/values-b+fi/strings_car.xml b/app/src/main/res-car/values-b+fi/strings_car.xml new file mode 100644 index 00000000000..f7ec058fae9 --- /dev/null +++ b/app/src/main/res-car/values-b+fi/strings_car.xml @@ -0,0 +1,54 @@ + + + Ladataan... + Virhe + Virhe ladattaessa sisältöä + Suosikit + Historia + Lataukset + Palveluntarjoaja + Nykyinen + Valikko + Etusivun sisältö + Ei sisältöä palveluntarjoajalta + Palveluntarjoajaa ei löytynyt + Lähde + Lähteet + Juoni + Näyttelijät + Virhe ladattaessa tietoja + Jaksot + Kausi + Kaudet + Jakso + Jaksoja ei löytynyt + Ei TV-sarja + Lisätty suosikkeihin + Poistettu suosikeista + Ei suosikkeja löytynyt + Aloitetaan toistoa... + Toistetaan + Linkkiä ei löytynyt + Toistettavaa sisältöä ei löytynyt + Tietoja ei voitu ladata + Virhe ladattaessa linkkejä + Sovita näyttöön + Täytä näyttö + Lähdettä ei löytynyt + Ei lähdettä saatavilla + Lähde valittu + Haku + Tuloksia ei löytynyt + \'Jatka katselua\' ei löytynyt + Latauksia ei löytynyt + Tiedostoa ei löytynyt + Kelvollista jaksoa ei löytynyt + Valitse palveluntarjoaja + Valittu + Kirjasto + Kirjanmerkit + Jatketaan %1$s... + Soittimen tila + Edistynyt soitin (hardware) + Yksinkertainen soitin (legacy) + diff --git a/app/src/main/res-car/values-b+fil/strings_car.xml b/app/src/main/res-car/values-b+fil/strings_car.xml new file mode 100644 index 00000000000..96df29c85e7 --- /dev/null +++ b/app/src/main/res-car/values-b+fil/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + Mode ng player + Advanced Player (hardware) + Simple Player (legacy) + diff --git a/app/src/main/res-car/values-b+fr/strings_car.xml b/app/src/main/res-car/values-b+fr/strings_car.xml new file mode 100644 index 00000000000..3a6604efc98 --- /dev/null +++ b/app/src/main/res-car/values-b+fr/strings_car.xml @@ -0,0 +1,54 @@ + + + Chargement... + Erreur + Erreur de chargement du contenu + Favoris + Historique + Téléchargements + Fournisseur + Actuel + Menu + Contenu d\'accueil + Aucun contenu du fournisseur + Fournisseur non trouvé + Source + Sources + Intrigue + Distribution + Erreur de chargement des détails + Liste des épisodes + Saison + Saisons + Épisode + Aucun épisode trouvé + Pas une série TV + Ajouté aux favoris + Retiré des favoris + Aucun favori trouvé + Démarrage de la lecture... + Lecture + Aucun lien trouvé + Aucun contenu lisible trouvé + Impossible de charger les détails + Erreur de chargement des liens + Adapter à l\'écran + Remplir l\'écran + Aucune source trouvée + Aucune source disponible + Source sélectionnée + Rechercher + Aucun résultat trouvé + Aucun élément \'Continuer à regarder\' trouvé + Aucun téléchargement trouvé + Fichier non trouvé + Aucun épisode valide trouvé + Sélectionner le fournisseur + Sélectionné + Bibliothèque + Marque-pages + Reprise %1$s... + Mode du lecteur + Lecteur avancé (hardware) + Lecteur simple (legacy) + diff --git a/app/src/main/res-car/values-b+gl/strings_car.xml b/app/src/main/res-car/values-b+gl/strings_car.xml new file mode 100644 index 00000000000..c95729bc19e --- /dev/null +++ b/app/src/main/res-car/values-b+gl/strings_car.xml @@ -0,0 +1,54 @@ + + + Cargando... + Erro + Erro cargando contido + Favoritos + Historial + Descargas + Provedor + Actual + Menú + Contido de inicio + Sen contido do provedor + Provedor non atopado + Fonte + Fontes + Argumento + Reparto + Erro cargando detalles + Lista de episodios + Tempada + Tempadas + Episodio + Non se atoparon episodios + Non é unha serie de TV + Engadido a favoritos + Eliminado de favoritos + Non se atoparon favoritos + Iniciando reprodución... + Reproducindo + Ligazón non atopada + Contido reproducible non atopado + Non se poden cargar os detalles + Erro cargando ligazóns + Axustar á pantalla + Encher pantalla + Fonte non atopada + Non hai fonte dispoñible + Fonte seleccionada + Buscar + Sen resultados + \'Continuar vendo\' non atopado + Sen descargas + Ficheiro non atopado + Sen episodio válido + Seleccionar provedor + Seleccionado + Biblioteca + Marcadores + Retomando %1$s... + Modo do reprodutor + Reprodutor avanzado (hardware) + Reprodutor simple (legacy) + diff --git a/app/src/main/res-car/values-b+gu/strings_car.xml b/app/src/main/res-car/values-b+gu/strings_car.xml new file mode 100644 index 00000000000..58de723dc7d --- /dev/null +++ b/app/src/main/res-car/values-b+gu/strings_car.xml @@ -0,0 +1,54 @@ + + + લોડ થઈ રહ્યું છે... + ભૂલ + સામગ્રી લોડ કરવામાં ભૂલ + મનપસંદ + ઇતિહાસ + ડાઉનલોડ્સ + પ્રદાતા + વર્તમાન + મેનુ + હોમ સામગ્રી + પ્રદાતા તરફથી કોઈ સામગ્રી નથી + પ્રદાતા મળ્યા નથી + સ્ત્રોત + સ્ત્રોતો + પ્લોટ + કલાકારો + વિગતો લોડ કરવામાં ભૂલ + એ એપિસોડ સૂચિ + સીઝન + સીઝન + એપિસોડ + કોઈ એપિસોડ મળ્યા નથી + ટીવી શ્રેણી નથી + મનપસંદમાં ઉમેર્યું + મનપસંદમાંથી દૂર કર્યું + કોઈ મનપસંદ મળ્યા નથી + પ્લેબેક શરૂ થઈ રહ્યું છે... + રમી રહ્યું છે + કોઈ લિંક મળી નથી + કોઈ રમી શકાય તેવી સામગ્રી મળી નથી + વિગતો લોડ કરવામાં અસમર્થ + લિંક્સ લોડ કરવામાં ભૂલ + સ્ક્રીન પર ફિટ કરો + સ્ક્રીન ભરો + કોઈ સ્ત્રોત મળ્યો નથી + કોઈ સ્ત્રોત ઉપલબ્ધ નથી + સ્ત્રોત પસંદ કર્યો + શોધો + કોઈ પરિણામ મળ્યા નથી + \'જોવાનું ચાલુ રાખો\' મળ્યું નથી + કોઈ ડાઉનલોડ્સ મળ્યા નથી + ફાઇલ મળી નથી + કોઈ માન્ય એપિસોડ મળ્યો નથી + પ્રદાતા પસંદ કરો + પસંદ કરેલ + લાઇબ્રેરી + બુકમાર્ક્સ + %1$s ફરી શરૂ કરી રહ્યું છે... + પ્લેયર મોડ + અદ્યતન પ્લેયર (hardware) + સરળ પ્લેયર (legacy) + diff --git a/app/src/main/res-car/values-b+hi/strings_car.xml b/app/src/main/res-car/values-b+hi/strings_car.xml new file mode 100644 index 00000000000..555469fe5fc --- /dev/null +++ b/app/src/main/res-car/values-b+hi/strings_car.xml @@ -0,0 +1,54 @@ + + + 로드 중... + गलती + सामग्री लोड हो रही है + पसंदीदा + इतिहास + डाउनलोड + प्रदाता + वर्तमान + मेन्यू + गृह सामग्री + प्रदाता से कोई सामग्री नहीं + प्रदाता नहीं मिला + स्रोत + सूत्रों + भूखंड + ढालना + विवरण लोड करने में त्रुटि + एपिसोड सूची + मौसम + मौसम + प्रसंग + कोई एपिसोड नहीं मिला + टीवी श्रृंखला नहीं + पसंदीदा में जोड़ा गया + पसंदीदा से हटाया गया + कोई पसंदीदा नहीं मिला + प्लेबैक प्रारंभ हो रहा है... + खेल + कोई लिंक नहीं मिला + कोई खेलने योग्य सामग्री नहीं मिली + विवरण लोड करने में असमर्थ + लिंक लोड करने में त्रुटि + स्क्रीन पर फिट करें + स्क्रीन भरें + कोई स्रोत नहीं मिला + कोई स्रोत उपलब्ध नहीं है + स्रोत चयनित + खोज + कोई परिणाम नहीं मिला + \'देखना जारी रखें\' नहीं मिला + कोई डाउनलोड नहीं मिला + फ़ाइल नहीं मिली + कोई मान्य एपिसोड नहीं मिला + प्रदाता का चयन करें + चयनित + पुस्तकालय + बुकमार्क + %1$s फिर से शुरू... + प्लेयर मोड + उन्नत प्लेयर (hardware) + सरल प्लेयर (legacy) + diff --git a/app/src/main/res-car/values-b+hr/strings_car.xml b/app/src/main/res-car/values-b+hr/strings_car.xml new file mode 100644 index 00000000000..f3ac76aaa55 --- /dev/null +++ b/app/src/main/res-car/values-b+hr/strings_car.xml @@ -0,0 +1,54 @@ + + + Učitavanje... + Greška + Greška pri učitavanju sadržaja + Favoriti + Povijest + Preuzimanja + Pružatelj + Trenutno + Izbornik + Početni sadržaj + Nema sadržaja od pružatelja + Pružatelj nije pronađen + Izvor + Izvori + Radnja + Glumci + Greška pri učitavanju detalja + Popis epizoda + Sezona + Sezone + Epizoda + Nema pronađenih epizoda + Nije TV serija + Dodano u favorite + Uklonjeno iz favorita + Nema favorita + Pokretanje reprodukcije... + Reprodukcija + Poveznica nije pronađena + Sadržaj za reprodukciju nije pronađen + Nije moguće učitati detalje + Greška pri učitavanju poveznica + Prilagodi ekranu + Ispuni ekran + Izvor nije pronađen + Izvor nije dostupan + Izvor odabran + Pretraži + Nema rezultata + \'Nastavi gledanje\' nije pronađeno + Nema preuzimanja + Datoteka nije pronađena + Nema važeće epizode + Odaberi pružatelja + Odabrano + Knjižnica + Oznake + Nastavljanje %1$s... + Način rada reproduktora + Napredni reproduktor (hardware) + Jednostavni reproduktor (legacy) + diff --git a/app/src/main/res-car/values-b+hu/strings_car.xml b/app/src/main/res-car/values-b+hu/strings_car.xml new file mode 100644 index 00000000000..5bcc91fa024 --- /dev/null +++ b/app/src/main/res-car/values-b+hu/strings_car.xml @@ -0,0 +1,54 @@ + + + Betöltés... + Hiba + Tartalom betöltési hiba + Kedvencek + Előzmények + Letöltések + Szolgáltató + Jelenlegi + Menü + Kezdőlap tartalom + Nincs tartalom a szolgáltatótól + Szolgáltató nem található + Forrás + Források + Cselekmény + Szereplők + Részletek betöltési hiba + Epizódlista + Évad + Évadok + Epizód + Nem található epizód + Nem TV sorozat + Hozzáadva a kedvencekhez + Eltávolítva a kedvencekből + Nincs kedvenc + Lejátszás indítása... + Lejátszás + Link nem található + Nincs lejátszható tartalom + Nem sikerült betölteni a részleteket + Link betöltési hiba + Képernyőhöz igazítás + Kitöltés + Forrás nem található + Nincs elérhető forrás + Forrás kiválasztva + Keresés + Nincs találat + Nincs \'Nézés folytatása\' + Nincs letöltés + Fájl nem található + Nincs érvényes epizód + Válassz szolgáltatót + Kiválasztva + Könyvtár + Könyvjelzők + %1$s folytatása... + Lejátszó mód + Fejlett lejátszó (hardware) + Egyszerű lejátszó (legacy) + diff --git a/app/src/main/res-car/values-b+hy/strings_car.xml b/app/src/main/res-car/values-b+hy/strings_car.xml new file mode 100644 index 00000000000..4ed1bc987b9 --- /dev/null +++ b/app/src/main/res-car/values-b+hy/strings_car.xml @@ -0,0 +1,54 @@ + + + Բեռնում... + Սխալ + Բովանդակության բեռնման սխալ + Ընտրյալներ + Պատմություն + Ներբեռնումներ + Մատակարար + Ընթացիկ + Մենյու + Գլխավոր էջի բովանդակություն + Մատակարարից բովանդակություն չկա + Մատակարարը չի գտնվել + Աղբյուր + Աղբյուրներ + Սյուժե + Դերասանական կազմ + Մանրամասների բեռնման սխալ + Սերիաների ցանկ + Եթերաշրջան + Եթերաշրջաններ + Սերիա + Սերիաներ չեն գտնվել + Հեռուստասերիալ չէ + Ավելացվել է ընտրյալներին + Հեռացվել է ընտրյալներից + Ընտրյալներ չեն գտնվել + Նվագարկման մեկնարկ... + Նվագարկվում է + Հղում չի գտնվել + Նվագարկվող բովանդակություն չի գտնվել + Հնարավոր չէ բեռնել մանրամասները + Հղումների բեռնման սխալ + Հարմարեցնել էկրանին + Լրացնել էկրանը + Աղբյուր չի գտնվել + Հասանելի աղբյուր չկա + Աղբյուրն ընտրված է + Որոնել + Արդյունքներ չեն գտնվել + \'Շարունակել դիտել\' չի գտնվել + Ներբեռնումներ չեն գտնվել + Ֆայլը չի գտնվել + Վավեր սերիա չի գտնվել + Ընտրեք մատակարար + Ընտրված է + Գրադարան + Էջանիշեր + Շարունակվում է %1$s... + Նvագարկdelays ռemode + Ünimage Նvager (hardware) + Պarzt Նvager (legacy) + diff --git a/app/src/main/res-car/values-b+in/strings_car.xml b/app/src/main/res-car/values-b+in/strings_car.xml new file mode 100644 index 00000000000..93d80696842 --- /dev/null +++ b/app/src/main/res-car/values-b+in/strings_car.xml @@ -0,0 +1,54 @@ + + + Memuat... + Kesalahan + Kesalahan memuat konten + Favorit + Riwayat + Unduhan + Penyedia + Saat Ini + Menu + Konten Beranda + Tidak ada konten dari penyedia + Penyedia tidak ditemukan + Sumber + Sumber + Plot + Pemeran + Kesalahan memuat detail + Daftar Episode + Musim + Musim + Episode + Tidak ada episode ditemukan + Bukan serial TV + Ditambahkan ke favorit + Dihapus dari favorit + Tidak ada favorit ditemukan + Memulai pemutaran... + Memutar + Tautan tidak ditemukan + Tidak ada konten yang dapat diputar + Tidak dapat memuat detail + Kesalahan memuat tautan + Pas ke Layar + Isi Layar + Sumber tidak ditemukan + Tidak ada sumber tersedia + Sumber dipilih + Cari + Tidak ada hasil ditemukan + \'Lanjutkan Menonton\' tidak ditemukan + Tidak ada unduhan ditemukan + Berkas tidak ditemukan + Tidak ada episode valid + Pilih Penyedia + Dipilih + Pustaka + Penanda + Melanjutkan %1$s... + Mode pemutar + Pemutar lanjutan (hardware) + Pemutar sederhana (legacy) + diff --git a/app/src/main/res-car/values-b+is/strings_car.xml b/app/src/main/res-car/values-b+is/strings_car.xml new file mode 100644 index 00000000000..0701e184830 --- /dev/null +++ b/app/src/main/res-car/values-b+is/strings_car.xml @@ -0,0 +1,54 @@ + + + Hleður... + Villa + Villa við að hlaða efni + Eftirlæti + Saga + Niðurhal + Veita + Núverandi + Valmynd + Heimaefni + Ekkert efni frá veitu + Veita fannst ekki + Heimild + Heimildir + Söguþráður + Leikarar + Villa við að hlaða upplýsingum + Þáttalisti + Sería + Seríur + Þáttur + Engir þættir fundust + Ekki sjónvarpsþáttaröð + Bætt við eftirlæti + Fjarlægt úr eftirlæti + Engin eftirlæti fundust + Hefur spilun... + Spilar + Enginn tengill fannst + Ekkert spilanlegt efni fannst + Gat ekki hlaðið upplýsingum + Villa við að hlaða tenglum + Passa á skjá + Fylla skjá + Engin heimild fannst + Engin heimild tiltæk + Heimild valin + Leita + Engar niðurstöður fundust + \'Halda áfram að horfa\' fannst ekki + Ekkert niðurhal fannst + Skrá fannst ekki + Enginn gildur þáttur fannst + Veldu veitu + Valið + Bókasafn + Bókamerki + Heldur áfram %1$s... + Spilarahamur + Háþróaður spilari (hardware) + Einfaldur spilari (legacy) + diff --git a/app/src/main/res-car/values-b+iw/strings_car.xml b/app/src/main/res-car/values-b+iw/strings_car.xml new file mode 100644 index 00000000000..89b20eede88 --- /dev/null +++ b/app/src/main/res-car/values-b+iw/strings_car.xml @@ -0,0 +1,54 @@ + + + טוען... + שגיאה + שגיאה בטעינת תוכן + מועדפים + היסטוריה + הורדות + ספק + נוכחי + תפריט + תוכן דף הבית + אין תוכן מהספק + ספק לא נמצא + מקור + מקורות + עלילה + שחקנים + שגיאה בטעינת פרטים + רשימת פרקים + עונה + עונות + פרק + לא נמצאו פרקים + לא סדרת טלוויזיה + נוסף למועדפים + הוסר מהמועדפים + לא נמצאו מועדפים + מתחיל ניגון... + מנגן + לא נמצא קישור + לא נמצא תוכן לניגון + לא ניתן לטעון פרטים + שגיאה בטעינת קישורים + התאם למסך + מלא מסך + מקור לא נמצא + אין מקור זמין + מקור נבחר + חיפוש + לא נמצאו תוצאות + \'המשך צפייה\' לא נמצא + לא נמצאו הורדות + קובץ לא נמצא + לא נמצא פרק תקין + בחר ספק + נבחר + ספרייה + סימניות + ממשיך %1$s... + מצב נגן + נגן מתקדם (hardware) + נגן פשוט (legacy) + diff --git a/app/src/main/res-car/values-b+ja/strings_car.xml b/app/src/main/res-car/values-b+ja/strings_car.xml new file mode 100644 index 00000000000..5fb4ca56e72 --- /dev/null +++ b/app/src/main/res-car/values-b+ja/strings_car.xml @@ -0,0 +1,54 @@ + + + 読み込み中... + エラー + コンテンツの読み込みエラー + お気に入り + 履歴 + ダウンロード + プロバイダー + 現在 + メニュー + ホームコンテンツ + プロバイダーからのコンテンツなし + プロバイダーが見つかりません + ソース + ソース + あらすじ + キャスト + 詳細の読み込みエラー + エピソードリスト + シーズン + シーズン + エピソード + エピソードが見つかりません + TV番組ではありません + お気に入りに追加しました + お気に入りから削除しました + お気に入りが見つかりません + 再生を開始しています... + 再生中 + リンクが見つかりません + 再生可能なコンテンツが見つかりません + 詳細を読み込めません + リンクの読み込みエラー + 画面に合わせる + 画面を埋める + ソースが見つかりません + 利用可能なソースがありません + ソースを選択しました + 検索 + 結果が見つかりません + 続きを見るコンテンツがありません + ダウンロードが見つかりません + ファイルが見つかりません + 有効なエピソードが見つかりません + プロバイダーを選択 + 選択済み + ライブラリ + ブックマーク + %1$s を再開しています... + プレーヤーモード + 高機能プレーヤー (hardware) + シンプルプレーヤー (legacy) + diff --git a/app/src/main/res-car/values-b+ka/strings_car.xml b/app/src/main/res-car/values-b+ka/strings_car.xml new file mode 100644 index 00000000000..78995bbb628 --- /dev/null +++ b/app/src/main/res-car/values-b+ka/strings_car.xml @@ -0,0 +1,54 @@ + + + ჩატვირთვა... + შეცდომა + შინაარსის ჩატვირთვის შეცდომა + რჩეულები + ისტორია + ჩამოტვირთვები + მომწოდებელი + მიმდინარე + მენიუ + მთავარი შინაარსი + შინაარსი მომწოდებლისგან არ არის + მომწოდებელი ვერ მოიძებნა + წყარო + წყაროები + სიუჟეტი + მსახიობები + დეტალების ჩატვირთვის შეცდომა + ეპიზოდების სია + სეზონი + სეზონები + ეპიზოდი + ეპიზოდები ვერ მოიძებნა + არ არის სატელევიზიო სერიალი + დაემატა რჩეულებში + ამოღებულია რჩეულებიდან + რჩეულები ვერ მოიძებნა + დაკვრის დაწყება... + დაკვრა + ბმული ვერ მოიძებნა + დასაკრავი შინაარსი ვერ მოიძებნა + დეტალების ჩატვირთვა ვერ მოხერხდა + ბმულების ჩატვირთვის შეცდომა + ეკრანზე მორგება + ეკრანის შევსება + წყარო ვერ მოიძებნა + წყარო არ არის ხელმისაწვდომი + წყარო არჩეულია + ძიება + შედეგები ვერ მოიძებნა + \'ყურების გაგრძელება\' ვერ მოიძებნა + ჩამოტვირთვები ვერ მოიძებნა + ფაილი ვერ მოიძებნა + ვალიდური ეპიზოდი ვერ მოიძებნა + აირჩიეთ მომწოდებელი + არჩეულია + ბიბლიოთეკა + სანიშნეები + გრძელდება %1$s... + პლეერის რეჟიმი + გაფართოებული პლეერი (hardware) + მარტივი პლეერი (legacy) + diff --git a/app/src/main/res-car/values-b+kk/strings_car.xml b/app/src/main/res-car/values-b+kk/strings_car.xml new file mode 100644 index 00000000000..a0ac5ddd372 --- /dev/null +++ b/app/src/main/res-car/values-b+kk/strings_car.xml @@ -0,0 +1,54 @@ + + + Жүктелуде... + Қате + Мазмұнды жүктеу қатесі + Таңдаулылар + Тарих + Жүктеулер + Провайдер + Ағымдағы + Мәзір + Басты бет мазмұны + Провайдерден мазмұн жоқ + Провайдер табылмады + Дереккөз + Дереккөздер + Сюжет + Актерлер + Мәліметтерді жүктеу қатесі + Эпизодтар тізімі + Маусым + Маусымдар + Эпизод + Эпизодтар табылмады + ТВ сериалы емес + Таңдаулыларға қосылды + Таңдаулылардан жойылды + Таңдаулылар табылмады + Ойнату басталуда... + Ойнатылуда + Сілтеме табылмады + Ойнатылатын мазмұн табылмады + Мәліметтерді жүктеу мүмкін емес + Сілтемелерді жүктеу қатесі + Экранға сыйғызу + Экранды толтыру + Дереккөз табылмады + Қолжетімді дереккөз жоқ + Дереккөз таңдалды + Іздеу + Нәтижелер табылмады + \'Көруді жалғастыру\' табылмады + Жүктеулер табылмады + Файл табылмады + Жарамды эпизод табылмады + Провайдерді таңдаңыз + Таңдалды + Кітапхана + Бетбелгілер + Жалғастырылуда %1$s... + Ойнатқыш режимі + Жетілдірілген ойнатқыш (hardware) + Қарапайым ойнатқыш (legacy) + diff --git a/app/src/main/res-car/values-b+km/strings_car.xml b/app/src/main/res-car/values-b+km/strings_car.xml new file mode 100644 index 00000000000..c0be0c2008f --- /dev/null +++ b/app/src/main/res-car/values-b+km/strings_car.xml @@ -0,0 +1,54 @@ + + + កំពុងផ្ទុក... + កំហុស + កំហុសក្នុងការផ្ទុកមាតិកា + ដែលចូលចិត្ត + ប្រវត្តិ + ការទាញយក + អ្នកផ្តល់សេវា + បច្ចុប្បន្ន + ម៉ឺនុយ + មាតិកាដើម + គ្មានមាតិកាពីអ្នកផ្តល់សេវា + រកមិនឃើញអ្នកផ្តល់សេវា + ប្រភព + ប្រភព + សាច់រឿង + តួសម្តែង + កំហុសក្នុងការផ្ទុកព័ត៌មានលម្អិត + បញ្ជីភាគ + រដូវកាល + រដូវកាល + ភាគ + រកមិនឃើញភាគ + មិនមែនជាស៊េរីទូរទស្សន៍ + បានបន្ថែមទៅការចូលចិត្ត + បានលុបចេញពីការចូលចិត្ត + រកមិនឃើញការចូលចិត្ត + កំពុងចាប់ផ្តើមការចាក់សារថ្មី... + កំពុងចាក់ + រកមិនឃើញតំណ + រកមិនឃើញមាតិកាដែលអាចចាក់បាន + មិនអាចផ្ទុកព័ត៌មានលម្អិត + កំហុសក្នុងការផ្ទុកតំណ + សមនឹងអេក្រង់ + ពេញអេក្រង់ + រកមិនឃើញប្រភព + គ្មានប្រភពដែលអាចប្រើបាន + បានជ្រើសរើសប្រភព + ស្វែងរក + រកមិនឃើញលទ្ធផល + រកមិនឃើញ \'បន្តមើល\' + រកមិនឃើញការទាញយក + រកមិនឃើញឯកសារ + រកមិនឃើញភាគដែលមានសុពលភាព + ជ្រើសរើសអ្នកផ្តល់សេវា + បានជ្រើសរើស + បណ្ណាល័យ + ចំណាំ + កំពុងបន្ត %1$s... + មុខងារកម្មវិធីចាក់ + កម្មវិធីចាក់កម្រិតខ្ពស់ (hardware) + កម្មវិធីចាក់ធម្មតា (legacy) + diff --git a/app/src/main/res-car/values-b+kn/strings_car.xml b/app/src/main/res-car/values-b+kn/strings_car.xml new file mode 100644 index 00000000000..34e628ca9bc --- /dev/null +++ b/app/src/main/res-car/values-b+kn/strings_car.xml @@ -0,0 +1,54 @@ + + + ಲೋಡ್ ಆಗುತ್ತಿದೆ... + ದೋಷ + ವಿಷಯವನ್ನು ಲೋಡ್ ಮಾಡುವಲ್ಲಿ ದೋಷ + ಮೆಚ್ಚಿನವುಗಳು + ಇತಿಹಾಸ + ಡೌನ್‌ಲೋಡ್‌ಗಳು + ಪೂರೈಕೆದಾರ + ಪ್ರಸ್ತುತ + ಮೆನು + ಮುಖಪುಟ ವಿಷಯ + ಪೂರೈಕೆದಾರರಿಂದ ಯಾವುದೇ ವಿಷಯವಿಲ್ಲ + ಪೂರೈಕೆದಾರ ಕಂಡುಬಂದಿಲ್ಲ + ಮೂಲ + ಮೂಲಗಳು + ಕಥಾವಸ್ತು + ಪಾತ್ರವರ್ಗ + ವಿವರಗಳನ್ನು ಲೋಡ್ ಮಾಡುವಲ್ಲಿ ದೋಷ + ಸಂಚಿಕೆ ಪಟ್ಟಿ + ಋತು + ಋತುಗಳು + ಸಂಚಿಕೆ + ಯಾವುದೇ ಸಂಚಿಕೆಗಳು ಕಂಡುಬಂದಿಲ್ಲ + ಟಿವಿ ಸರಣಿಯಲ್ಲ + ಮೆಚ್ಚಿನವುಗಳಿಗೆ ಸೇರಿಸಲಾಗಿದೆ + ಮೆಚ್ಚಿನವುಗಳಿಂದ ತೆಗೆದುಹಾಕಲಾಗಿದೆ + ಯಾವುದೇ ಮೆಚ್ಚಿನವುಗಳು ಕಂಡುಬಂದಿಲ್ಲ + ಪ್ಲೇಬ್ಯಾಕ್ ಪ್ರಾರಂಭವಾಗುತ್ತಿದೆ... + ಪ್ಲೇ ಆಗುತ್ತಿದೆ + ಯಾವುದೇ ಲಿಂಕ್ ಕಂಡುಬಂದಿಲ್ಲ + ಯಾವುದೇ ಪ್ಲೇ ಮಾಡಬಹುದಾದ ವಿಷಯ ಕಂಡುಬಂದಿಲ್ಲ + ವಿವರಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ + ಲಿಂಕ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡುವಲ್ಲಿ ದೋಷ + ಪರದೆಗೆ ಹೊಂದಿಸಿ + ಪರದೆ ತುಂಬಿಸಿ + ಯಾವುದೇ ಮೂಲ ಕಂಡುಬಂದಿಲ್ಲ + ಯಾವುದೇ ಮೂಲ ಲಭ್ಯವಿಲ್ಲ + ಮೂಲ ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ + ಹುಡುಕಿ + ಯಾವುದೇ ಫಲಿತಾಂಶಗಳು ಕಂಡುಬಂದಿಲ್ಲ + \'ವೀಕ್ಷಣೆ ಮುಂದುವರಿಸಿ\' ಕಂಡುಬಂದಿಲ್ಲ + ಯಾವುದೇ ಡೌನ್‌ಲೋಡ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ + ಫೈಲ್ ಕಂಡುಬಂದಿಲ್ಲ + ಯಾವುದೇ ಮಾನ್ಯ ಸಂಚಿಕೆ ಕಂಡುಬಂದಿಲ್ಲ + ಪೂರೈಕೆದಾರರನ್ನು ಆಯ್ಕೆಮಾಡಿ + ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ + ಗ್ರಂಥಾಲಯ + ಬುಕ್‌ಮಾರ್ಕ್‌ಗಳು + %1$s ಪುನರಾರಂಭವಾಗುತ್ತಿದೆ... + ಪ್ಲೇಯರ್ ಮೋಡ್ + ಸುಧಾರಿತ ಪ್ಲೇಯರ್ (hardware) + ಸರಳ ಪ್ಲೇಯರ್ (legacy) + diff --git a/app/src/main/res-car/values-b+ko/strings_car.xml b/app/src/main/res-car/values-b+ko/strings_car.xml new file mode 100644 index 00000000000..880a1d0911b --- /dev/null +++ b/app/src/main/res-car/values-b+ko/strings_car.xml @@ -0,0 +1,54 @@ + + + 로드 중... + 오류 + 콘텐츠 로드 오류 + 즐겨찾기 + 기록 + 다운로드 + 제공자 + 현재 + 메뉴 + 홈 콘텐츠 + 제공자의 콘텐츠 없음 + 제공자를 찾을 수 없음 + 소스 + 소스 + 줄거리 + 출연진 + 세부 정보 로드 오류 + 에피소드 목록 + 시즌 + 시즌 + 에피소드 + 에피소드 없음 + TV 시리즈 아님 + 즐겨찾기에 추가됨 + 즐겨찾기에서 제거됨 + 즐겨찾기 없음 + 재생 시작 중... + 재생 중 + 링크 없음 + 재생 가능한 콘텐츠 없음 + 세부 정보를 로드할 수 없음 + 링크 로드 오류 + 화면에 맞춤 + 화면 채우기 + 소스 없음 + 사용 가능한 소스 없음 + 소스 선택됨 + 검색 + 결과 없음 + 이어보기 없음 + 다운로드 없음 + 파일 없음 + 유효한 에피소드 없음 + 제공자 선택 + 선택됨 + 라이브러리 + 북마크 + %1$s 이어보는 중... + 플레이어 모드 + 고급 플레이어 (hardware) + 간단한 플레이어 (legacy) + diff --git a/app/src/main/res-car/values-b+ky/strings_car.xml b/app/src/main/res-car/values-b+ky/strings_car.xml new file mode 100644 index 00000000000..7b960be9b15 --- /dev/null +++ b/app/src/main/res-car/values-b+ky/strings_car.xml @@ -0,0 +1,54 @@ + + + Жүктөө... + Ката + Мазмунду жүктөө катасы + Сүйүктүүлөр + Тарых + Жүктөөлөр + Провайдер + Учурдагы + Меню + Башкы мазмун + Провайдерден мазмун жок + Провайдер табылган жок + Булак + Булактар + Сюжет + Актерлор + Маалыматтарды жүктөө катасы + Эпизоддор тизмеси + Сезон + Сезондор + Эпизод + Эпизоддор табылган жок + ТВ сериалы эмес + Сүйүктүүлөргө кошулду + Сүйүктүүлөрдөн алынды + Сүйүктүүлөр табылган жок + Ойнотуу башталууда... + Ойнотулууда + Шилтеме табылган жок + Ойнотула турган мазмун табылган жок + Маалыматтарды жүктөө мүмкүн эмес + Шилтемелерди жүктөө катасы + Экранга тууралоо + Экранды толтуруу + Булак табылган жок + Жеткиликтүү булак жок + Булак тандалды + Издөө + Жыйынтык табылган жок + \'Көрүүнү улантуу\' табылган жок + Жүктөөлөр табылган жок + Файл табылган жок + Жарактуу эпизод табылган жок + Провайдерди тандоо + Тандалды + Китепкана + Кыстармалар + Улантылууда %1$s... + Ойноткуч режими + Өнүккөн ойноткуч (hardware) + Жөнөкөй ойноткуч (legacy) + diff --git a/app/src/main/res-car/values-b+lo/strings_car.xml b/app/src/main/res-car/values-b+lo/strings_car.xml new file mode 100644 index 00000000000..9217cf641d7 --- /dev/null +++ b/app/src/main/res-car/values-b+lo/strings_car.xml @@ -0,0 +1,54 @@ + + + ກຳລັງໂຫຼດ... + ຂໍ້ຜິດພາດ + ເກີດຂໍ້ຜິດພາດໃນການໂຫຼດເນື້ອຫາ + ລາຍການທີ່ມັກ + ປະຫວັດການເຂົ້າຊົມ + ການດາວໂຫຼດ + ຜູ້ໃຫ້ບໍລິການ + ປັດຈຸບັນ + ເມນູ + ເນື້ອຫາໜ້າຫຼັກ + ບໍ່ມີເນື້ອຫາຈາກຜູ້ໃຫ້ບໍລິການ + ບໍ່ພົບຜູ້ໃຫ້ບໍລິການ + ແຫຼ່ງທີ່ມາ + ແຫຼ່ງທີ່ມາ + ເນື້ອເລື່ອງ + ນັກສະແດງ + ເກີດຂໍ້ຜິດພາດໃນການໂຫຼດລາຍລະອຽດ + ລາຍການຕອນ + ຊີຊັນ + ຊີຊັນ + ຕອນ + ບໍ່ພົບຕອນ + ບໍ່ແມ່ນຊີຣີໂທລະທັດ + ເພີ່ມເຂົ້າໃນລາຍການທີ່ມັກແລ້ວ + ລົບອອກຈາກລາຍການທີ່ມັກແລ້ວ + ບໍ່ພົບລາຍການທີ່ມັກ + ກຳລັງເລີ່ມການຫຼິ້ນ... + ກຳລັງຫຼິ້ນ + ບໍ່ພົບລິ້ງ + ບໍ່ພົບເນື້ອຫາທີ່ສາມາດຫຼິ້ນໄດ້ + ບໍ່ສາມາດໂຫຼດລາຍລະອຽດໄດ້ + ເກີດຂໍ້ຜິດພາດໃນການໂຫຼດລິ້ງ + ປັບໃຫ້ພໍດີກັບໜ້າຈໍ + ຂະຫຍາຍເຕັ້ນໜ້າຈໍ + ບໍ່ພົບແຫຼ່ງທີ່ມາ + ບໍ່ມີແຫຼ່ງທີ່ມາທີ່ໃຊ້ໄດ້ + ເລືອກແຫຼ່ງທີ່ມາແລ້ວ + ຄົ້ນຫາ + ບໍ່ພົບຜົນການຄົ້ນຫາ + ບໍ່ພົບ \'ເບິ່ງຕໍ່\' + ບໍ່ພົບການດາວໂຫຼດ + ບໍ່ພົບໄຟລ໌ + ບໍ່ພົບຕອນທີ່ຖືກຕ້ອງ + ເລືອກຜູ້ໃຫ້ບໍລິການ + ທີ່ເລືອກ + ຫ້ອງສະໝຸດ + ບຸກມາກ + ກຳລັງເບິ່ງຕໍ່ %1$s... + ໂໝດເຄື່ອງຫຼິ້ນ + ເຄື່ອງຫຼິ້ນຂັ້ນສູງ (hardware) + ເຄື່ອງຫຼິ້ນງ່າຍດາຍ (legacy) + diff --git a/app/src/main/res-car/values-b+lt/strings_car.xml b/app/src/main/res-car/values-b+lt/strings_car.xml new file mode 100644 index 00000000000..39e9da6fa6b --- /dev/null +++ b/app/src/main/res-car/values-b+lt/strings_car.xml @@ -0,0 +1,54 @@ + + + Įkeliama... + Klaida + Klaida įkeliant turinį + Mėgstamiausi + Istorija + Atsisiuntimai + Tiekėjas + Dabartinis + Meniu + Pradžios turinys + Nėra turinio iš tiekėjo + Tiekėjas nerastas + Šaltinis + Šaltiniai + Siužetas + Aktoriai + Klaida įkeliant informaciją + Epizodų sąrašas + Sezonas + Sezonai + Epizodas + Epizodų nerasta + Ne TV serialas + Pridėta prie mėgstamiausių + Pašalinta iš mėgstamiausių + Mėgstamiausių nerasta + Pradedamas atkūrimas... + Atkuriama + Nuoroda nerasta + Atkuriamo turinio nerasta + Nepavyko įkelti informacijos + Klaida įkeliant nuorodas + Pritaikyti ekranui + Užpildyti ekraną + Šaltinis nerastas + Nėra galimų šaltinių + Šaltinis pasirinktas + Paieška + Rezultatų nerasta + \'Tęsti žiūrėjimą\' nerasta + Atsisiuntimų nerasta + Failas nerastas + Nerastas tinkamas epizodas + Pasirinkite tiekėją + Pasirinkta + Biblioteka + Žymos + Tęsiama %1$s... + Grotuvo režimas + Išplėstinis grotuvas (hardware) + Paprastas grotuvas (legacy) + diff --git a/app/src/main/res-car/values-b+lv/strings_car.xml b/app/src/main/res-car/values-b+lv/strings_car.xml new file mode 100644 index 00000000000..5e21459679b --- /dev/null +++ b/app/src/main/res-car/values-b+lv/strings_car.xml @@ -0,0 +1,54 @@ + + + Ielādē... + Kļūda + Satura ielādes kļūda + Izlase + Vēsture + Lejupielādes + Piegādātājs + Pašreizējais + Izvēlne + Sākuma saturs + Nav satura no piegādātāja + Piegādātājs nav atrasts + Avots + Avoti + Sižets + Lomas + Kļūda ielādējot informāciju + Epizožu saraksts + Sezona + Sezonas + Epizode + Epizodes nav atrastas + Nav TV seriāls + Pievienots izlasei + Noņemts no izlases + Izlase nav atrasta + Sāk atskaņošanu... + Atskaņo + Saite nav atrasta + Nav atrasts atskaņojams saturs + Nevar ielādēt informāciju + Kļūda ielādējot saites + Pielāgot ekrānam + Aizpildīt ekrānu + Avots nav atrasts + Nav pieejamu avotu + Avots izvēlēts + Meklēt + Rezultāti nav atrasti + \'Turpināt skatīties\' nav atrasts + Lejupielādes nav atrastas + Fails nav atrasts + Nav atrasta derīga epizode + Izvēlēties piegādātāju + Izvēlēts + Bibliotēka + Grāmatzīmes + Atsāk %1$s... + Atskaņotāja režīms + Uzlabots atskaņotājs (hardware) + Vienkāršs atskaņotājs (legacy) + diff --git a/app/src/main/res-car/values-b+mk/strings_car.xml b/app/src/main/res-car/values-b+mk/strings_car.xml new file mode 100644 index 00000000000..2e5c5020840 --- /dev/null +++ b/app/src/main/res-car/values-b+mk/strings_car.xml @@ -0,0 +1,54 @@ + + + Вчитување... + Грешка + Грешка при вчитување содржина + Омилени + Историја + Преземања + Добавувач + Тековно + Мени + Почетна содржина + Нема содржина од добавувачот + Добавувачот не е пронајден + Извор + Извори + Дејство + Улоги + Грешка при вчитување детали + Листа на епизоди + Сезона + Сезони + Епизода + Не се пронајдени епизоди + Не е ТВ серија + Додадено во омилени + Отстрането од омилени + Не се пронајдени омилени + Започнување репродукција... + Репродукција + Врската не е пронајдена + Не е пронајдена содржина за репродукција + Не може да се вчитаат детали + Грешка при вчитување врски + Вклопи во екран + Пополни екран + Изворот не е пронајден + Нема достапен извор + Изворот е избран + Пребарување + Не се пронајдени резултати + \'Продолжи со гледање\' не е пронајдено + Не се пронајдени преземања + Датотеката не е пронајдена + Не е пронајдена валидна епизода + Избери добавувач + Избрано + Библиотека + Обележувачи + Продолжување %1$s... + Режим на плеер + Напреден плеер (hardware) + Едноставен плеер (legacy) + diff --git a/app/src/main/res-car/values-b+ml/strings_car.xml b/app/src/main/res-car/values-b+ml/strings_car.xml new file mode 100644 index 00000000000..decbff5265f --- /dev/null +++ b/app/src/main/res-car/values-b+ml/strings_car.xml @@ -0,0 +1,54 @@ + + + ലോഡ് ചെയ്യുന്നു... + പിശക് + ഉള്ളടക്കം ലോഡ് ചെയ്യുന്നതിൽ പിശക് + പ്രിയപ്പെട്ടവ + ചരിത്രം + ഡൗൺലോഡുകൾ + ദാതാവ് + നിലവിലുള്ള + മെനു + ഹോം ഉള്ളടക്കം + ദാതാവിൽ നിന്ന് ഉള്ളടക്കമില്ല + ദാതാവിനെ കണ്ടെത്തിയില്ല + ഉറവിടം + ഉറവിടങ്ങൾ + ഇതിവൃത്തം + അഭിനേതാക്കൾ + വിശദാംശങ്ങൾ ലോഡ് ചെയ്യുന്നതിൽ പിശക് + എപ്പിസോഡ് ലിസ്റ്റ് + സീസൺ + സീസണുകൾ + എപ്പിസോഡ് + എപ്പിസോഡുകളൊന്നും കണ്ടെത്തിയില്ല + ടിവി സീരീസ് അല്ല + പ്രിയപ്പെട്ടവയിലേക്ക് ചേർത്തു + പ്രിയപ്പെട്ടവയിൽ നിന്ന് നീക്കം ചെയ്തു + പ്രിയപ്പെട്ടവയൊന്നും കണ്ടെത്തിയില്ല + പ്ലേബാക്ക് ആരംഭിക്കുന്നു... + പ്ലേ ചെയ്യുന്നു + ലിങ്ക് കണ്ടെത്തിയില്ല + പ്ലേ ചെയ്യാവുന്ന ഉള്ളടക്കമൊന്നും കണ്ടെത്തിയില്ല + വിശദാംശങ്ങൾ ലോഡ് ചെയ്യാനായില്ല + ലിങ്കുകൾ ലോഡ് ചെയ്യുന്നതിൽ പിശക് + സ്‌ക്രീനിലേക്ക് യോജിപ്പിക്കുക + സ്‌ക്രീൻ നിറയ്ക്കുക + ഉറവിടം കണ്ടെത്തിയില്ല + ഉറവിടമൊന്നും ലഭ്യമല്ല + ഉറവിടം തിരഞ്ഞെടുത്തു + തിരയുക + ഫലങ്ങളൊന്നും കണ്ടെത്തിയില്ല + \'കാണുന്നത് തുടരുക\' കണ്ടെത്തിയില്ല + ഡൗൺലോഡുകളൊന്നും കണ്ടെത്തിയില്ല + ഫയൽ കണ്ടെത്തിയില്ല + സാധുവായ എപ്പിസോഡ് കണ്ടെത്തിയില്ല + ദാതാവിനെ തിരഞ്ഞെടുക്കുക + തിരഞ്ഞെടുത്തു + ലൈബ്രറി + ബുക്ക്മാർക്കുകൾ + %1$s പുനരാരംഭിക്കുന്നു... + പ്ലേയർ മോഡ് + വിപുലമായ പ്ലേയർ (hardware) + ലളിതമായ പ്ലേയർ (legacy) + diff --git a/app/src/main/res-car/values-b+mn/strings_car.xml b/app/src/main/res-car/values-b+mn/strings_car.xml new file mode 100644 index 00000000000..bace5513014 --- /dev/null +++ b/app/src/main/res-car/values-b+mn/strings_car.xml @@ -0,0 +1,54 @@ + + + Ачаалж байна... + Алдаа + Контент ачаалахад алдаа гарлаа + Таалагдсан + Түүх + Татаж авсан + Нийлүүлэгч + Одоогийн + Цэс + Нүүр хуудасны контент + Нийлүүлэгчээс контент алга + Нийлүүлэгч олдсонгүй + Эх сурвалж + Эх сурвалжууд + Үйл явдал + Дүрүүд + Дэлгэрэнгүйг ачаалахад алдаа гарлаа + Ангийн жагсаалт + Бүлэг + Бүлгүүд + Анги + Анги олдсонгүй + ТВ цуврал биш + Таалагдсанд нэмлээ + Таалагдсанаас хаслаа + Таалагдсан контент олдсонгүй + Тоглуулж эхэлж байна... + Тоглуулж байна + Холбоос олдсонгүй + Тоглуулах контент олдсонгүй + Дэлгэрэнгүйг ачаалах боломжгүй + Холбоос ачаалахад алдаа гарлаа + Дэлгэцэнд тааруулах + Дэлгэц дүүргэх + Эх сурвалж олдсонгүй + Боломжит эх сурвалж алга + Эх сурвалж сонгогдсон + Хайх + Үр дүн олдсонгүй + \'Үргэлжлүүлж үзэх\' олдсонгүй + Таталт олдсонгүй + Файл олдсонгүй + Хүчинтэй анги олдсонгүй + Нийлүүлэгч сонгох + Сонгогдсон + Сан + Хавчуурга + %1$s үргэлжлүүлж байна... + Тоглуулагчийн горим + Дэвшилтэт тоглуулагч (hardware) + Энгийн тоглуулагч (legacy) + diff --git a/app/src/main/res-car/values-b+mr/strings_car.xml b/app/src/main/res-car/values-b+mr/strings_car.xml new file mode 100644 index 00000000000..e831faeaa7f --- /dev/null +++ b/app/src/main/res-car/values-b+mr/strings_car.xml @@ -0,0 +1,54 @@ + + + लोड होत आहे... + त्रुटी + सामग्री लोड करताना त्रुटी + आवडते + इतिहास + डाउनलोड + पुरवठादार + वर्तमान + मेनू + होम सामग्री + पुरवठादाराकडून कोणतीही सामग्री नाही + पुरवठादार सापडला नाही + स्त्रोत + स्त्रोत + कथानक + कलाकार + तपशील लोड करताना त्रुटी + एपिसोड यादी + सीझन + सीझन + एपिसोड + कोणतेही एपिसोड सापडले नाहीत + टीव्ही मालिका नाही + आवडत्यांमध्ये जोडले + आवडत्यांमधून काढले + कोणतेही आवडते सापडले नाहीत + प्लेबॅक सुरू होत आहे... + प्ले होत आहे + कोणतीही लिंक सापडली नाही + कोणतीही प्ले करण्यायोग्य सामग्री सापडली नाही + तपशील लोड करण्यास अक्षम + लिंक लोड करताना त्रुटी + स्क्रीनवर बसवा + स्क्रीन भरा + कोणताही स्त्रोत सापडला नाही + कोणताही स्त्रोत उपलब्ध नाही + स्त्रोत निवडला + शोधा + कोणतेही परिणाम सापडले नाहीत + \'पाहणे सुरू ठेवा\' सापडले नाही + कोणतेही डाउनलोड सापडले नाहीत + फाइल सापडली नाही + कोणताही वैध एपिसोड सापडला नाही + पुरवठादार निवडा + निवडलेले + ग्रंथालय + बुकमार्क + %1$s पुन्हा सुरू करत आहे... + प्लेयर मोड + प्रगत प्लेयर (hardware) + साधा प्लेयर (legacy) + diff --git a/app/src/main/res-car/values-b+ms/strings_car.xml b/app/src/main/res-car/values-b+ms/strings_car.xml new file mode 100644 index 00000000000..2dd0d227d26 --- /dev/null +++ b/app/src/main/res-car/values-b+ms/strings_car.xml @@ -0,0 +1,54 @@ + + + Memuatkan... + Ralat + Ralat memuatkan kandungan + Kegemaran + Sejarah + Muat Turun + Penyedia + Semasa + Menu + Kandungan Laman Utama + Tiada kandungan dari penyedia + Penyedia tidak ditemui + Sumber + Sumber + Plot + Pelakon + Ralat memuatkan butiran + Senarai Episod + Musim + Musim + Episod + Tiada episod ditemui + Bukan siri TV + Ditambah ke kegemaran + Dibuang dari kegemaran + Tiada kegemaran ditemui + Memulakan main balik... + Memainkan + Pautan tidak ditemui + Tiada kandungan boleh dimainkan ditemui + Tidak dapat memuatkan butiran + Ralat memuatkan pautan + Muat ke Skrin + Penuhi Skrin + Tiada sumber ditemui + Tiada sumber tersedia + Sumber dipilih + Cari + Tiada hasil ditemui + \'Teruskan Menonton\' tidak ditemui + Tiada muat turun ditemui + Fail tidak ditemui + Tiada episod sah ditemui + Pilih Penyedia + Dipilih + Perpustakaan + Penanda Buku + Menyambung semula %1$s... + Mod pemain + Pemain lanjutan (hardware) + Pemain ringkas (legacy) + diff --git a/app/src/main/res-car/values-b+mt/strings_car.xml b/app/src/main/res-car/values-b+mt/strings_car.xml new file mode 100644 index 00000000000..3788b154997 --- /dev/null +++ b/app/src/main/res-car/values-b+mt/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + Modalità tal-player + Player avvanzat (hardware) + Player sempliċi (legacy) + diff --git a/app/src/main/res-car/values-b+my/strings_car.xml b/app/src/main/res-car/values-b+my/strings_car.xml new file mode 100644 index 00000000000..35781a43478 --- /dev/null +++ b/app/src/main/res-car/values-b+my/strings_car.xml @@ -0,0 +1,54 @@ + + + Loading... + အမှား + အကြောင်းအရာကို ဆွဲယူရာတွင် အမှားဖြစ်နေသည် + အကြိုက်ဆုံးများ + မှတ်တမ်း + ဒေါင်းလုဒ်များ + ပံ့ပိုးသူ + လက်ရှိ + မီနူး + ပင်မစာမျက်နှာ + ပံ့ပိုးသူထံမှ အကြောင်းအရာ မရှိပါ + ပံ့ပိုးသူကို မတွေ့ပါ + အရင်းအမြစ် + အရင်းအမြစ်များ + ဇာတ်လမ်းအကျဉ်း + သရုပ်ဆောင်များ + အသေးစိတ်အချက်အလက်များကို ဆွဲယူရာတွင် အမှားဖြစ်နေသည် + အပိုင်းများ + ရာသီ + ရာသီများ + အပိုင်း + အပိုင်းများ မတွေ့ပါ + တီဗီစီးရီး မဟုတ်ပါ + အကြိုက်ဆုံးများသို့ ထည့်ပြီးပါပြီ + အကြိုက်ဆုံးများမှ ဖယ်ရှားလိုက်ပါပြီ + အကြိုက်ဆုံးများ မတွေ့ပါ + ဖွင့်နေသည်... + ဖွင့်နေသည် + လင့်ခ်မတွေ့ပါ + ဖွင့်နိုင်သော အကြောင်းအရာ မတွေ့ပါ + အသေးစိတ်အချက်အလက်များကို မဆွဲယူနိုင်ပါ + လင့်ခ်များကို ဆွဲယူရာတွင် အမှားဖြစ်နေသည် + မျက်နှာပြင်နှင့် အံကိုက် + မျက်နှာပြင်အပြည့် + အရင်းအမြစ် မတွေ့ပါ + ရရှိနိုင်သော အရင်းအမြစ် မရှိပါ + အရင်းအမြစ်ကို ရွေးချယ်လိုက်သည် + ရှာဖွေရန် + ရလဒ်မတွေ့ပါ + \'ဆက်လက်ကြည့်ရှုရန်\' မတွေ့ပါ + ဒေါင်းလုဒ်များ မတွေ့ပါ + ဖိုင်မတွေ့ပါ + မှန်ကန်သော အပိုင်း မတွေ့ပါ + ပံ့ပိုးသူကို ရွေးချယ်ပါ + ရွေးချယ်ပြီး + စာကြည့်တိုက် + မှတ်သားမှုများ + %1$s ကို ဆက်လက်ကြည့်ရှုနေသည်... + ပလေယာမုဒ် + အဆင့်မြင့်ပလေယာ (hardware) + ရိုးရှင်းပလေယာ (legacy) + diff --git a/app/src/main/res-car/values-b+ne/strings_car.xml b/app/src/main/res-car/values-b+ne/strings_car.xml new file mode 100644 index 00000000000..68e36153948 --- /dev/null +++ b/app/src/main/res-car/values-b+ne/strings_car.xml @@ -0,0 +1,54 @@ + + + लोड हुँदैछ... + त्रुटि + सामग्री लोड गर्दा त्रुटि + मनपर्नेहरू + इतिहास + डाउनलोडहरू + प्रदायक + हालको + मेनु + गृह सामग्री + प्रदायकबाट कुनै सामग्री छैन + प्रदायक फेला परेन + स्रोत + स्रोतहरू + कथा + कलाकारहरू + विवरण लोड गर्दा त्रुटि + एपिसोड सूची + सिजन + सिजनहरू + एपिसोड + कुनै एपिसोड फेला परेन + टिभी शृङ्खला होइन + मनपर्नेमा थपियो + मनपर्नेबाट हटाइयो + कुनै मनपर्ने फेला परेन + प्लेब्याक सुरु हुँदैछ... + बजाउँदै + कुनै लिङ्क फेला परेन + कुनै बजाउन योग्य सामग्री फेला परेन + विवरण लोड गर्न असक्षम + लिङ्कहरू लोड गर्दा त्रुटि + स्क्रिनमा मिलाउनुहोस् + स्क्रिन भर्नुहोस् + कुनै स्रोत फेला परेन + कुनै स्रोत उपलब्ध छैन + स्रोत चयन गरियो + खोज्नुहोस् + कुनै नतिजा फेला परेन + \'हेर्न जारी राख्नुहोस्\' फेला परेन + कुनै डाउनलोड फेला परेन + फाइल फेला परेन + कुनै मान्य एपिसोड फेला परेन + प्रदायक चयन गर्नुहोस् + चयन गरिएको + पुस्तकालय + बुकमार्कहरू + %1$s सुचारु हुँदैछ... + प्लेयर मोड + उन्नत प्लेयर (hardware) + सरल प्लेयर (legacy) + diff --git a/app/src/main/res-car/values-b+nl/strings_car.xml b/app/src/main/res-car/values-b+nl/strings_car.xml new file mode 100644 index 00000000000..5a148d13add --- /dev/null +++ b/app/src/main/res-car/values-b+nl/strings_car.xml @@ -0,0 +1,54 @@ + + + Laden... + Fout + Fout bij laden inhoud + Favorieten + Geschiedenis + Downloads + Aanbieder + Huidig + Menu + De inhoud van het huis + Geen inhoud van aanbieder + Aanbieder niet gevonden + Bron + Bronnen + Verhaal + Cast + Fout bij laden details + Afleveringenlijst + Seizoen + Seizoenen + Aflevering + Geen afleveringen gevonden + Geen TV-serie + Toegevoegd aan favorieten + Verwijderd uit favorieten + Geen favorieten gevonden + Afspelen starten... + Afspelen + Geen link gevonden + Geen afspeelbare inhoud gevonden + Kan details niet laden + Fout bij laden links + Passend maken + Scherm vullen + Geen bron gevonden + Geen bron beschikbaar + Bron geselecteerd + Zoeken + Geen resultaten gevonden + Geen \'Verder kijken\' gevonden + Geen downloads gevonden + Bestand niet gevonden + Geen geldige aflevering gevonden + Selecteer aanbieder + Geselecteerd + Bibliotheek + Bladwijzers + Hervatten %1$s... + Spelermodus + Geavanceerde speler (hardware) + Eenvoudige speler (legacy) + diff --git a/app/src/main/res-car/values-b+nn/strings_car.xml b/app/src/main/res-car/values-b+nn/strings_car.xml new file mode 100644 index 00000000000..26d202b9f04 --- /dev/null +++ b/app/src/main/res-car/values-b+nn/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + Spelar-modus + Avansert spelar (hardware) + Enkel spelar (legacy) + diff --git a/app/src/main/res-car/values-b+no/strings_car.xml b/app/src/main/res-car/values-b+no/strings_car.xml new file mode 100644 index 00000000000..dd09aa9f9fa --- /dev/null +++ b/app/src/main/res-car/values-b+no/strings_car.xml @@ -0,0 +1,54 @@ + + + Laster inn... + Feil + Feil ved lasting av innhold + Favoritter + Historikk + Nedlastinger + Leverandør + Nåværende + Meny + Hjem-innhold + Ingen innhold fra leverandør + Leverandør ikke funnet + Kilde + Kilder + Handling + Skuespillere + Feil ved lasting av detaljer + Episodeliste + Sesong + Sesonger + Episode + Ingen episoder funnet + Ikke en TV-serie + Lagt til i favoritter + Fjernet fra favoritter + Ingen favoritter funnet + Starter avspilling... + Spiller av + Ingen lenke funnet + Ingen spillbart innhold funnet + Kan ikke laste detaljer + Feil ved lasting av lenker + Tilpass til skjerm + Fyll skjerm + Ingen kilde funnet + Ingen kilde tilgjengelig + Kilde valgt + Søk + Ingen resultater funnet + \'Fortsett å se\' ikke funnet + Ingen nedlastinger funnet + Fil ikke funnet + Ingen gyldig episode funnet + Velg leverandør + Valgt + Bibliotek + Bokmerker + Gjenopptar %1$s... + Spillermodus + Avansert spiller (hardware) + Enkel spiller (legacy) + diff --git a/app/src/main/res-car/values-b+or/strings_car.xml b/app/src/main/res-car/values-b+or/strings_car.xml new file mode 100644 index 00000000000..dac8a851d93 --- /dev/null +++ b/app/src/main/res-car/values-b+or/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + ପ୍ଲେୟର ମୋଡ + ଉନ୍ନତ ପ୍ଲେୟର (hardware) + ସରଳ ପ୍ଲେୟର (legacy) + diff --git a/app/src/main/res-car/values-b+pa/strings_car.xml b/app/src/main/res-car/values-b+pa/strings_car.xml new file mode 100644 index 00000000000..15d7699f4ef --- /dev/null +++ b/app/src/main/res-car/values-b+pa/strings_car.xml @@ -0,0 +1,54 @@ + + + ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ... + ਗਲਤੀ + ਸਮੱਗਰੀ ਲੋਡ ਕਰਨ ਵਿੱਚ ਗਲਤੀ + ਮਨਪਸੰਦ + ਇਤਿਹਾਸ + ਡਾਊਨਲੋਡ + ਪ੍ਰਦਾਤਾ + ਮੌਜੂਦਾ + ਮੀਨੂ + ਘਰੇਲੂ ਸਮੱਗਰੀ + ਪ੍ਰਦਾਤਾ ਤੋਂ ਕੋਈ ਸਮੱਗਰੀ ਨਹੀਂ + ਪ੍ਰਦਾਤਾ ਨਹੀਂ ਮਿਲਿਆ + ਸਰੋਤ + ਸਰੋਤ + ਕਹਾਣੀ + ਕਲਾਕਾਰ + ਵੇਰਵੇ ਲੋਡ ਕਰਨ ਵਿੱਚ ਗਲਤੀ + ਐਪੀਸੋਡ ਸੂਚੀ + ਸੀਜ਼ਨ + ਸੀਜ਼ਨ + ਐਪੀਸੋਡ + ਕੋਈ ਐਪੀਸੋਡ ਨਹੀਂ ਮਿਲਿਆ + ਟੀਵੀ ਲੜੀਵਾਰ ਨਹੀਂ + ਮਨਪਸੰਦ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ + ਮਨਪਸੰਦ ਤੋਂ ਹਟਾਇਆ ਗਿਆ + ਕੋਈ ਮਨਪਸੰਦ ਨਹੀਂ ਮਿਲਿਆ + ਪਲੇਬੈਕ ਸ਼ੁਰੂ ਹੋ ਰਿਹਾ ਹੈ... + ਖੇਡ ਰਿਹਾ ਹੈ + ਕੋਈ ਲਿੰਕ ਨਹੀਂ ਮਿਲਿਆ + ਕੋਈ ਖੇਡਣ ਯੋਗ ਸਮੱਗਰੀ ਨਹੀਂ ਮਿਲੀ + ਵੇਰਵੇ ਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਮਰੱਥ + ਲਿੰਕ ਲੋਡ ਕਰਨ ਵਿੱਚ ਗਲਤੀ + ਸਕ੍ਰੀਨ \'ਤੇ ਫਿੱਟ ਕਰੋ + ਸਕ੍ਰੀਨ ਭਰੋ + ਕੋਈ ਸਰੋਤ ਨਹੀਂ ਮਿਲਿਆ + ਕੋਈ ਸਰੋਤ ਉਪਲਬਧ ਨਹੀਂ + ਸਰੋਤ ਚੁਣਿਆ ਗਿਆ + ਖੋਜ + ਕੋਈ ਨਤੀਜਾ ਨਹੀਂ ਮਿਲਿਆ + \'ਦੇਖਣਾ ਜਾਰੀ ਰੱਖੋ\' ਨਹੀਂ ਮਿਲਿਆ + ਕੋਈ ਡਾਊਨਲੋਡ ਨਹੀਂ ਮਿਲਿਆ + ਫਾਈਲ ਨਹੀਂ ਮਿਲੀ + ਕੋਈ ਵੈਧ ਐਪੀਸੋਡ ਨਹੀਂ ਮਿਲਿਆ + ਪ੍ਰਦਾਤਾ ਚੁਣੋ + ਚੁਣਿਆ ਗਿਆ + ਲਾਇਬ੍ਰੇਰੀ + ਬੁੱਕਮਾਰਕ + %1$s ਮੁੜ ਸ਼ੁਰੂ ਹੋ ਰਿਹਾ ਹੈ... + ਪਲੇਅਰ ਮੋਡ + ਉੱਨਤ ਪਲੇਅਰ (hardware) + ਸਰਲ ਪਲੇਅਰ (legacy) + diff --git a/app/src/main/res-car/values-b+pl/strings_car.xml b/app/src/main/res-car/values-b+pl/strings_car.xml new file mode 100644 index 00000000000..e7eaf336451 --- /dev/null +++ b/app/src/main/res-car/values-b+pl/strings_car.xml @@ -0,0 +1,54 @@ + + + Ładowanie... + Błąd + Błąd ładowania treści + Ulubione + Historia + Pobrane + Dostawca + Obecny + Menu + Treść główna + Brak treści od dostawcy + Nie znaleziono dostawcy + Źródło + Źródła + Fabuła + Obsada + Błąd ładowania szczegółów + Lista odcinków + Sezon + Sezony + Odcinek + Nie znaleziono odcinków + To nie jest serial TV + Dodano do ulubionych + Usunięto z ulubionych + Nie znaleziono ulubionych + Rozpoczynanie odtwarzania... + Odtwarzanie + Nie znaleziono łącza + Nie znaleziono treści do odtworzenia + Nie można załadować szczegółów + Błąd ładowania łączy + Dopasuj do ekranu + Wypełnij ekran + Nie znaleziono źródła + Brak dostępnych źródeł + Wybrano źródło + Szukaj + Brak wyników + Nie znaleziono „Oglądaj dalej” + Brak pobranych plików + Plik nie znaleziony + Brak poprawnego odcinka + Wybierz dostawcę + Wybrano + Biblioteka + Zakładki + Wznawianie %1$s... + Tryb odtwarzacza + Zaawansowany odtwarzacz (hardware) + Prosty odtwarzacz (legacy) + diff --git a/app/src/main/res-car/values-b+pt+BR/strings_car.xml b/app/src/main/res-car/values-b+pt+BR/strings_car.xml new file mode 100644 index 00000000000..d3db7d5aa25 --- /dev/null +++ b/app/src/main/res-car/values-b+pt+BR/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + Modo do reprodutor + Reprodutor avançado (hardware) + Reprodutor simples (legacy) + diff --git a/app/src/main/res-car/values-b+pt/strings_car.xml b/app/src/main/res-car/values-b+pt/strings_car.xml new file mode 100644 index 00000000000..d91b0ff7eda --- /dev/null +++ b/app/src/main/res-car/values-b+pt/strings_car.xml @@ -0,0 +1,54 @@ + + + Carregando... + Erro + Erro ao carregar conteúdo + Favoritos + Histórico + Downloads + Provedor + Atual + Menu + Conteúdo da página inicial + Nenhum conteúdo do provedor + Provedor não encontrado + Fonte + Fontes + Enredo + Elenco + Erro ao carregar detalhes + Lista de episódios + Temporada + Temporadas + Episódio + Nenhum episódio encontrado + Não é uma série de TV + Adicionado aos favoritos + Removido dos favoritos + Nenhum favorito encontrado + Iniciando reprodução... + Reproduzindo + Nenhum link encontrado + Nenhum conteúdo reproduzível encontrado + Não foi possível carregar os detalhes + Erro ao carregar links + Ajustar à tela + Preencher tela + Nenhuma fonte encontrada + Nenhuma fonte disponível + Fonte selecionada + Buscar + Nenhum resultado encontrado + Nenhum item \'Continuar assistindo\' encontrado + Nenhum download encontrado + Arquivo não encontrado + Nenhum episódio válido encontrado + Selecionar provedor + Selecionado + Biblioteca + Marcadores + Retomando %1$s... + Modo do reprodutor + Reprodutor avançado (hardware) + Reprodutor simples (legacy) + diff --git a/app/src/main/res-car/values-b+qt/strings_car.xml b/app/src/main/res-car/values-b+qt/strings_car.xml new file mode 100644 index 00000000000..c0a59138075 --- /dev/null +++ b/app/src/main/res-car/values-b+qt/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + Player mode + Advanced Player (hardware) + Simple Player (legacy) + diff --git a/app/src/main/res-car/values-b+ro/strings_car.xml b/app/src/main/res-car/values-b+ro/strings_car.xml new file mode 100644 index 00000000000..df9281b8bd1 --- /dev/null +++ b/app/src/main/res-car/values-b+ro/strings_car.xml @@ -0,0 +1,54 @@ + + + Se încarcă... + Eroare + Eroare la încărcarea conținutului + Favorite + Istoric + Descărcări + Furnizor + Curent + Meniu + Conținut acasă + Niciun conținut de la furnizor + Furnizorul nu a fost găsit + Sursă + Surse + Intrigă + Distribuție + Eroare la încărcarea detaliilor + Listă episoade + Sezon + Sezoane + Episod + Nu s-au găsit episoade + Nu este serial TV + Adăugat la favorite + Eliminat din favorite + Nu s-au găsit favorite + Se începe redarea... + Se redă + Nu s-a găsit link + Nu s-a găsit conținut care poate fi redat + Nu se pot încărca detaliile + Eroare la încărcarea linkurilor + Potrivire la ecran + Umplere ecran + Nu s-a găsit sursă + Nicio sursă disponibilă + Sursă selectată + Căutare + Niciun rezultat găsit + Nu s-a găsit \'Continuare vizionare\' + Nu s-au găsit descărcări + Fișierul nu a fost găsit + Nu s-a găsit episod valid + Selectează furnizor + Selectat + Bibliotecă + Marcaje + Se reia %1$s... + Modul playerului + Player avansat (hardware) + Player simplu (legacy) + diff --git a/app/src/main/res-car/values-b+ru/strings_car.xml b/app/src/main/res-car/values-b+ru/strings_car.xml new file mode 100644 index 00000000000..f3f2fc8c7a0 --- /dev/null +++ b/app/src/main/res-car/values-b+ru/strings_car.xml @@ -0,0 +1,54 @@ + + + Загрузка... + Ошибка + Ошибка загрузки контента + Избранное + История + Загрузки + Провайдер + Текущий + Меню + Контент главной + Нет контента от провайдера + Провайдер не найден + Источник + Источники + Сюжет + Актеры + Ошибка загрузки деталей + Список эпизодов + Сезон + Сезоны + Эпизод + Эпизоды не найдены + Не сериал + Добавлено в избранное + Удалено из избранного + Избранное не найдено + Запуск воспроизведения... + Воспроизведение + Ссылка не найдена + Нет воспроизводимого контента + Не удалось загрузить детали + Ошибка загрузки ссылок + По размеру экрана + Заполнить экран + Источник не найден + Нет доступных источников + Источник выбран + Поиск + Результаты не найдены + Нет "Продолжить просмотр" + Загрузки не найдены + Файл не найден + Нет валидного эпизода + Выбрать провайдера + Выбрано + Библиотека + Закладки + Возобновление %1$s... + Режим плеера + Расширенный плеер (hardware) + Простой плеер (legacy) + diff --git a/app/src/main/res-car/values-b+si/strings_car.xml b/app/src/main/res-car/values-b+si/strings_car.xml new file mode 100644 index 00000000000..a325c1ed6ae --- /dev/null +++ b/app/src/main/res-car/values-b+si/strings_car.xml @@ -0,0 +1,54 @@ + + + පූරණය වෙමින්... + දෝෂය + අන්තර්ගතය පූරණය කිරීමේ දෝෂය + ප්‍රියතමයන් + ඉතිහාසය + බාගැනීම් + සැපයුම්කරු + වත්මන් + මෙනුව + මුල් පිටුව අන්තර්ගතය + සැපයුම්කරුගෙන් අන්තර්ගතයක් නැත + සැපයුම්කරු හමු නොවිණි + මූලාශ්‍රය + මූලාශ්‍ර + කතාව + නළු නිළියන් + විස්තර පූරණය කිරීමේ දෝෂය + කොටස් ලැයිස්තුව + වාරය + වාර + කොටස + කොටස් හමු නොවිණි + රූපවාහිනී කතා මාලාවක් නොවේ + ප්‍රියතමයන් වෙත එක් කරන ලදී + ප්‍රියතමයන්ගෙන් ඉවත් කරන ලදී + ප්‍රියතමයන් හමු නොවිණි + ප්‍රතිණිර්මාණය ආරම්භ කරමින්... + වාදනය වෙමින් + සබැඳියක් හමු නොවිණි + වාදනය කළ හැකි අන්තර්ගතයක් හමු නොවිණි + විස්තර පූරණය කළ නොහැක + සබැඳි පූරණය කිරීමේ දෝෂය + තිරයට ගැලපේ + තිරය පුරවන්න + මූලාශ්‍රයක් හමු නොවිණි + මූලාශ්‍රයක් නොමැත + මූලාශ්‍රය තෝරා ගන්නා ලදී + සොයන්න + ප්‍රතිඵල හමු නොවිණි + \'නැරඹීම අඛණ්ඩව\' හමු නොවිණි + බාගැනීම් හමු නොවිණි + ගොනුව හමු නොවිණි + වලංගු කොටසක් හමු නොවිණි + සැපයුම්කරු තෝරන්න + තෝරා ගන්නා ලදී + පුස්තකාලය + පොත් යොමු + %1$s නැවත ආරම්භ කරමින්... + ප්ලේයර් ප්‍රකාරය + උසස් ප්ලේයර් (hardware) + සරල ප්ලේයර් (legacy) + diff --git a/app/src/main/res-car/values-b+sk/strings_car.xml b/app/src/main/res-car/values-b+sk/strings_car.xml new file mode 100644 index 00000000000..e58f0fc798e --- /dev/null +++ b/app/src/main/res-car/values-b+sk/strings_car.xml @@ -0,0 +1,54 @@ + + + Načítavanie... + Chyba + Chyba pri načítavaní obsahu + Obľúbené + História + Sťahovania + Poskytovateľ + Aktuálne + Menu + Domovský obsah + Žiadny obsah od poskytovateľa + Poskytovateľ nenájdený + Zdroj + Zdroje + Dej + Obsadenie + Chyba pri načítavaní podrobností + Zoznam epizód + Séria + Série + Epizóda + Nenašli sa žiadne epizódy + Nie je to TV seriál + Pridané do obľúbených + Odstránené z obľúbených + Žiadne obľúbené položky + Spúšťanie prehrávania... + Prehrávanie + Odkaz sa nenašiel + Žiadny prehrávateľný obsah + Nedá sa načítať podrobnosti + Chyba pri načítavaní odkazov + Prispôsobiť na obrazovku + Vyplniť obrazovku + Zdroj sa nenašiel + Žiadny zdroj k dispozícii + Zdroj vybraný + Hľadať + Žiadne výsledky sa nenašli + \'Pokračovať v sledovaní\' sa nenašlo + Žiadne sťahovania + Súbor nenájdený + Žiadna platná epizóda + Vybrať poskytovateľa + Vybrané + Knižnica + Záložky + Obnovovanie %1$s... + Režim prehrávača + Pokročilý prehrávač (hardware) + Jednoduchý prehrávač (legacy) + diff --git a/app/src/main/res-car/values-b+sl/strings_car.xml b/app/src/main/res-car/values-b+sl/strings_car.xml new file mode 100644 index 00000000000..9fbbf4e61b3 --- /dev/null +++ b/app/src/main/res-car/values-b+sl/strings_car.xml @@ -0,0 +1,54 @@ + + + Nalaganje... + Napaka + Napaka pri nalaganju vsebine + Priljubljene + Zgodovina + Prenosi + Ponudnik + Trenutno + Meni + Domača vsebina + Ni vsebine od ponudnika + Ponudnik ni najden + Vir + Viri + Zgodba + Igralska zasedba + Napaka pri nalaganju podrobnosti + Seznam epizod + Sezona + Sezone + Epizoda + Ni najdenih epizod + Ni TV serija + Dodano med priljubljene + Odstranjeno iz priljubljenih + Ni priljubljenih + Začenjam predvajanje... + Predvajam + Povezava ni najdena + Ni vsebine za predvajanje + Ni bilo mogoče naložiti podrobnosti + Napaka pri nalaganju povezav + Prilagodi zaslonu + Napolni zaslon + Vir ni najden + Ni razpoložljivih virov + Vir izbran + Iskanje + Ni rezultatov + \'Nadaljuj z ogledom\' ni najdeno + Ni prenosov + Datoteka ni najdena + Ni veljavne epizode + Izberi ponudnika + Izbrano + Knjižnica + Zaznamki + Nadaljujem %1$s... + Način predvajalnika + Napredni predvajalnik (hardware) + Preprost predvajalnik (legacy) + diff --git a/app/src/main/res-car/values-b+so/strings_car.xml b/app/src/main/res-car/values-b+so/strings_car.xml new file mode 100644 index 00000000000..9cbf3788ae3 --- /dev/null +++ b/app/src/main/res-car/values-b+so/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + Qaabka ciyaaryahanka + Ciyaaryahan horumarsan (hardware) + Ciyaaryahan fudud (legacy) + diff --git a/app/src/main/res-car/values-b+sq/strings_car.xml b/app/src/main/res-car/values-b+sq/strings_car.xml new file mode 100644 index 00000000000..51f63e0a705 --- /dev/null +++ b/app/src/main/res-car/values-b+sq/strings_car.xml @@ -0,0 +1,54 @@ + + + Duke ngarkuar... + Gabim + Gabim në ngarkimin e përmbajtjes + Të preferuarat + Historia + Shkarkimet + Ofruesi + Aktual + Menu + Përmbajtja e kreut + Nuk ka përmbajtje nga ofruesi + Ofruesi nuk u gjet + Burimi + Burimet + Përmbajtja + Aktorët + Gabim në ngarkimin e detajeve + Lista e episodeve + Sezoni + Sezonet + Episodi + Nuk u gjetën episode + Nuk është serial TV + U shtua te të preferuarat + U hoq nga të preferuarat + Nuk u gjetën të preferuara + Duke filluar luajtjen... + Duke luajtur + Nuk u gjet lidhje + Nuk u gjet përmbajtje për t\'u luajtur + Nuk mund të ngarkohen detajet + Gabim në ngarkimin e lidhjeve + Përshtat në ekran + Mbush ekranin + Nuk u gjet burim + Asnjë burim i disponueshëm + Burimi u zgjodh + Kërko + Nuk u gjetën rezultate + \'Vazhdo shikimin\' nuk u gjet + Nuk u gjetën shkarkime + Skedari nuk u gjet + Nuk u gjet episod i vlefshëm + Zgjidh ofruesin + E zgjedhur + Biblioteka + Faqerojtësit + Duke vazhduar %1$s... + Modaliteti i luajtësit + Luajtës i avancuar (hardware) + Luajtës i thjeshtë (legacy) + diff --git a/app/src/main/res-car/values-b+sr/strings_car.xml b/app/src/main/res-car/values-b+sr/strings_car.xml new file mode 100644 index 00000000000..8685213cc06 --- /dev/null +++ b/app/src/main/res-car/values-b+sr/strings_car.xml @@ -0,0 +1,54 @@ + + + Učitavanje... + Greška + Greška pri učitavanju sadržaja + Favoriti + Istorija + Preuzimanja + Provajder + Trenutno + Meni + Početni sadržaj + Nema sadržaja od provajdera + Provajder nije pronađen + Izvor + Izvori + Radnja + Uloge + Greška pri učitavanju detalja + Lista epizoda + Sezona + Sezone + Epizoda + Nema pronađenih epizoda + Nije TV serija + Dodato u favorite + Uklonjeno iz favorita + Nema favorita + Pokretanje reprodukcije... + Reprodukcija + Link nije pronađen + Sadržaj za reprodukciju nije pronađen + Nije moguće učitati detalje + Greška pri učitavanju linkova + Prilagodi ekranu + Popuni ekran + Izvor nije pronađen + Izvor nije dostupan + Izvor izabran + Pretraga + Nema rezultata + \'Nastavi gledanje\' nije pronađeno + Nema preuzimanja + Datoteka nije pronađena + Nema važeće epizode + Izaberi provajdera + Izabrano + Biblioteka + Obeleživači + Nastavljanje %1$s... + Режим плејера + Напредни плејер (hardware) + Једноставни плејер (legacy) + diff --git a/app/src/main/res-car/values-b+sv/strings_car.xml b/app/src/main/res-car/values-b+sv/strings_car.xml new file mode 100644 index 00000000000..e847b41b660 --- /dev/null +++ b/app/src/main/res-car/values-b+sv/strings_car.xml @@ -0,0 +1,54 @@ + + + Laddar... + Fel + Fel vid laddning av innehåll + Favoriter + Historik + Nedladdningar + Leverantör + Nuvarande + Meny + Heminnehåll + Inget innehåll från leverantör + Leverantör hittades inte + Källa + Källor + Handling + Skådespelare + Fel vid laddning av detaljer + Avsnittslista + Säsong + Säsonger + Avsnitt + Inga avsnitt hittades + Inte en TV-serie + Tillagd i favoriter + Borttagen från favoriter + Inga favoriter hittades + Startar uppspelning... + Spelar + Ingen länk hittades + Inget spelbart innehåll hittades + Kunde inte ladda detaljer + Fel vid laddning av länkar + Anpassa till skärm + Fyll skärm + Ingen källa hittades + Ingen källa tillgänglig + Källa vald + Sök + Inga resultat hittades + Inget \'Fortsätt titta\' hittades + Inga nedladdningar hittades + Filen hittades inte + Inget giltigt avsnitt hittades + Välj leverantör + Vald + Bibliotek + Bokmärken + Återupptar %1$s... + Spelarläge + Avancerad spelare (hardware) + Enkel spelare (legacy) + diff --git a/app/src/main/res-car/values-b+sw/strings_car.xml b/app/src/main/res-car/values-b+sw/strings_car.xml new file mode 100644 index 00000000000..10aacd3ee07 --- /dev/null +++ b/app/src/main/res-car/values-b+sw/strings_car.xml @@ -0,0 +1,54 @@ + + + Inapakia... + Hitilafu + Hitilafu katika kupakia maudhui + Vipendwa + Historia + Vipakuliwa + Mtoaji + Sasa + Menyu + Maudhui ya Nyumbani + Hakuna maudhui kutoka kwa mtoaji + Mtoaji hajapatikana + Chanzo + Vyanzo + Hadithi + Waigizaji + Hitilafu katika kupakia maelezo + Orodha ya Vipindi + Msimu + Misimu + Kipindi + Hakuna vipindi vilivyopatikana + Si mfululizo wa TV + Imeongezwa kwenye vipendwa + Imeondolewa kwenye vipendwa + Hakuna vipendwa vilivyopatikana + Inaanza kucheza... + Inacheza + Hakuna kiungo kilichopatikana + Hakuna maudhui ya kucheza yaliyopatikana + Imeshindwa kupakia maelezo + Hitilafu katika kupakia viungo + Toshea kwenye Skrini + Jaza Skrini + Hakuna chanzo kilichopatikana + Hakuna chanzo kinachopatikana + Chanzo kimechaguliwa + Tafuta + Hakuna matokeo yaliyopatikana + \'Endelea Kutazama\' haijapatikana + Hakuna vipakuliwa vilivyopatikana + Faili haijapatikana + Hakuna kipindi halali kilichopatikana + Chagua Mtoaji + Imechaguliwa + Maktaba + Alamisho + Inaendelea %1$s... + Hali ya kicheza + Kicheza cha juu (hardware) + Kicheza rahisi (legacy) + diff --git a/app/src/main/res-car/values-b+ta/strings_car.xml b/app/src/main/res-car/values-b+ta/strings_car.xml new file mode 100644 index 00000000000..3658b3ed3f8 --- /dev/null +++ b/app/src/main/res-car/values-b+ta/strings_car.xml @@ -0,0 +1,54 @@ + + + ஏற்றுகிறது... + பிழை + உள்ளடக்கத்தை ஏற்றுவதில் பிழை + விருப்பங்கள் + வரலாறு + பதிவிறக்கங்கள் + வழங்குநர் + தற்போதைய + மெனு + முகப்பு உள்ளடக்கம் + வழங்குநரிடமிருந்து உள்ளடக்கம் இல்லை + வழங்குநரைக் காணவில்லை + மூலம் + மூலங்கள் + கதைக்களம் + நடிப்பு + விவரங்களை ஏற்றுவதில் பிழை + அத்தியாயப் பட்டியல் + பருவம் + பருவங்கள் + அத்தியாயம் + அத்தியாயங்கள் எதுவும் காணப்படவில்லை + தொலைக்காட்சித் தொடர் அல்ல + விருப்பங்களில் சேர்க்கப்பட்டது + விருப்பங்களிலிருந்து நீக்கப்பட்டது + விருப்பங்கள் எதுவும் இல்லை + இயக்கத்தைத் தொடங்குகிறது... + இயங்குகிறது + இணைப்பு எதுவும் காணப்படவில்லை + இயக்கக்கூடிய உள்ளடக்கம் எதுவும் இல்லை + விவரங்களை ஏற்ற முடியவில்லை + இணைப்புகளை ஏற்றுவதில் பிழை + திரைக்குப் பொருத்து + திரையை நிரப்பு + மூலம் எதுவும் காணப்படவில்லை + மூலம் எதுவும் கிடைக்கவில்லை + மூலம் தேர்ந்தெடுக்கப்பட்டது + தேடு + முடிவுகள் எதுவும் இல்லை + \'தொடர்ந்து பார்க்க\' எதுவும் இல்லை + பதிவிறக்கங்கள் எதுவும் இல்லை + கோப்பைக் காணவில்லை + செல்லுபடியாகும் அத்தியாயம் எதுவும் இல்லை + வழங்குநரைத் தேர்ந்தெடு + தேர்ந்தெடுக்கப்பட்டது + நூலகம் + புத்தகக்குறிகள் + %1$s தொடர்கிறது... + பிளேயர் பயன்முறை + மேம்பட்ட பிளேயர் (hardware) + எளிய பிளேயர் (legacy) + diff --git a/app/src/main/res-car/values-b+te/strings_car.xml b/app/src/main/res-car/values-b+te/strings_car.xml new file mode 100644 index 00000000000..37ee4235217 --- /dev/null +++ b/app/src/main/res-car/values-b+te/strings_car.xml @@ -0,0 +1,54 @@ + + + లోడ్ అవుతోంది... + లోపం + కంటెంట్ లోడ్ చేయడంలో లోపం + ఇష్టమైనవి + చరిత్ర + డౌన్‌లోడ్‌లు + ప్రొవైడర్ + ప్రస్తుత + మెనూ + హోమ్ కంటెంట్ + ప్రొవైడర్ నుండి కంటెంట్ లేదు + ప్రొవైడర్ కనుగొనబడలేదు + మూలం + మూలాలు + కథాంశం + నటీనటులు + వివరాలు లోడ్ చేయడంలో లోపం + ఎపిసోడ్ జాబితా + సీజన్ + సీజన్లు + ఎపిసోడ్ + ఎపిసోడ్‌లు కనుగొనబడలేదు + టీవీ సిరీస్ కాదు + ఇష్టమైన వాటికి జోడించబడింది + ఇష్టమైన వాటి నుండి తీసివేయబడింది + ఇష్టమైనవి కనుగొనబడలేదు + ప్లేబ్యాక్ ప్రారంభమవుతోంది... + ప్లే అవుతోంది + లింక్ కనుగొనబడలేదు + ప్లే చేయగల కంటెంట్ కనుగొనబడలేదు + వివరాలు లోడ్ చేయడం సాధ్యం కాలేదు + లింక్‌లు లోడ్ చేయడంలో లోపం + స్క్రీన్‌కు సరిపోల్చండి + స్క్రీన్‌ను నింపండి + మూలం కనుగొనబడలేదు + మూలం అందుబాటులో లేదు + మూలం ఎంపిక చేయబడింది + శోధించు + ఫలితాలు కనుగొనబడలేదు + \'చూడటం కొనసాగించు\' కనుగొనబడలేదు + డౌన్‌లోడ్‌లు కనుగొనబడలేదు + ఫైల్ కనుగొనబడలేదు + చెల్లుబాటు అయ్యే ఎపిసోడ్ కనుగొనబడలేదు + ప్రొవైడర్‌ను ఎంచుకోండి + ఎంపిక చేయబడింది + లైబ్రరీ + బుక్‌మార్క్‌లు + %1$s పునఃప్రారంభించబడుతోంది... + ప్లేయర్ మోడ్ + అధునాతన ప్లేయర్ (hardware) + సాధారణ ప్లేయర్ (legacy) + diff --git a/app/src/main/res-car/values-b+tg/strings_car.xml b/app/src/main/res-car/values-b+tg/strings_car.xml new file mode 100644 index 00000000000..8567613166a --- /dev/null +++ b/app/src/main/res-car/values-b+tg/strings_car.xml @@ -0,0 +1,54 @@ + + + Боргирӣ... + Хатогӣ + Хатогӣ дар боргирии муҳтаво + Дӯстдоштаҳо + Таърих + Боргириҳо + Провайдер + Ҷорӣ + Меню + Муҳтавои хона + Ҳеҷ муҳтаво аз провайдер нест + Провайдер ёфт нашуд + Манбаъ + Манбаъҳо + Сюжет + Ҳунармандон + Хатогӣ дар боргирии тафсилот + Рӯйхати қисмҳо + Мавсим + Мавсимҳо + Қисм + Ҳеҷ қисм ёфт нашуд + Силсилафилм нест + Ба дӯстдоштаҳо илова шуд + Аз дӯстдоштаҳо хориҷ шуд + Ҳеҷ дӯстдошта ёфт нашуд + Оғози пахш... + Дар ҳоли пахш + Ҳеҷ пайванд ёфт нашуд + Ҳеҷ муҳтавои қобили пахш ёфт нашуд + Тафсилот боргирӣ нашуд + Хатогӣ дар боргирии пайвандҳо + Ба экран мувофиқ кардан + Пур кардани экран + Ҳеҷ манбаъ ёфт нашуд + Ҳеҷ манбаъ дастрас нест + Манбаъ интихоб шуд + Ҷустуҷӯ + Ҳеҷ натиҷа ёфт нашуд + \'Идомаи тамошо\' ёфт нашуд + Ҳеҷ боргирӣ ёфт нашуд + Файл ёфт нашуд + Ҳеҷ қисми дуруст ёфт нашуд + Провайдерро интихоб кунед + Интихобшуда + Китобхона + Хабовҳо + Идомаи %1$s... + Ҳолати плеер + Плеери пешрафта (hardware) + Плеери оддӣ (legacy) + diff --git a/app/src/main/res-car/values-b+th/strings_car.xml b/app/src/main/res-car/values-b+th/strings_car.xml new file mode 100644 index 00000000000..83be05d2331 --- /dev/null +++ b/app/src/main/res-car/values-b+th/strings_car.xml @@ -0,0 +1,54 @@ + + + กำลังโหลด... + ข้อผิดพลาด + ข้อผิดพลาดในการโหลดเนื้อหา + รายการโปรด + ประวัติ + ดาวน์โหลด + ผู้ให้บริการ + ปัจจุบัน + เมนู + เนื้อหาหน้าแรก + ไม่มีเนื้อหาจากผู้ให้บริการ + ไม่พบผู้ให้บริการ + แหล่งที่มา + แหล่งที่มา + เนื้อเรื่อง + นักแสดง + ข้อผิดพลาดในการโหลดรายละเอียด + รายการตอน + ซีซั่น + ซีซั่น + ตอน + ไม่พบตอน + ไม่ใช่ทีวีซีรีส์ + เพิ่มในรายการโปรดแล้ว + ลบออกจากรายการโปรดแล้ว + ไม่พบรายการโปรด + กำลังเริ่มเล่น... + กำลังเล่น + ไม่พบลิงก์ + ไม่พบเนื้อหาที่เล่นได้ + ไม่สามารถโหลดรายละเอียดได้ + ข้อผิดพลาดในการโหลดลิงก์ + พอดีหน้าจอ + เต็มหน้าจอ + ไม่พบแหล่งที่มา + ไม่มีแหล่งที่มาที่ใช้ได้ + เลือกแหล่งที่มาแล้ว + ค้นหา + ไม่พบผลลัพธ์ + ไม่พบ \'ดูต่อ\' + ไม่พบการดาวน์โหลด + ไม่พบไฟล์ + ไม่พบตอนที่ถูกต้อง + เลือกผู้ให้บริการ + ที่เลือก + ห้องสมุด + บุ๊กมาร์ก + กำลังดูต่อ %1$s... + โหมดเครื่องเล่น + เครื่องเล่นขั้นสูง (hardware) + เครื่องเล่นอย่างง่าย (legacy) + diff --git a/app/src/main/res-car/values-b+ti/strings_car.xml b/app/src/main/res-car/values-b+ti/strings_car.xml new file mode 100644 index 00000000000..96f759a86b8 --- /dev/null +++ b/app/src/main/res-car/values-b+ti/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + ናይ ተጻዋቲ ሁነታ + ዝለዓለ ተጻዋቲ (hardware) + ቀሊል ተጻዋቲ (legacy) + diff --git a/app/src/main/res-car/values-b+tl/strings_car.xml b/app/src/main/res-car/values-b+tl/strings_car.xml new file mode 100644 index 00000000000..f0f755a42c3 --- /dev/null +++ b/app/src/main/res-car/values-b+tl/strings_car.xml @@ -0,0 +1,54 @@ + + + Naglo-load... + Error + Error sa pag-load ng nilalaman + Mga Paborito + Kasaysayan + Mga Download + Tagapagbigay + Kasalukuyan + Menu + Nilalaman ng tahanan + Walang nilalaman mula sa tagapagbigay + Hindi natagpuan ang tagapagbigay + Pinagmulan + Mga Pinagmulan + Kuwento + Tauhan + Error sa pag-load ng mga detalye + Listahan ng Episodyo + Season + Mga Season + Episodyo + Walang natagpuang episodyo + Hindi serye sa TV + Idinagdag sa mga paborito + Inalis sa mga paborito + Walang natagpuang paborito + Sinisimulan ang pag-playback... + Gumagana + Walang natagpuang link + Walang natagpuang puwedeng i-play na nilalaman + Hindi ma-load ang mga detalye + Error sa pag-load ng mga link + Pagkasyahin sa Screen + Punuin ang Screen + Walang napiling pinagmulan + Walang magagamit na pinagmulan + Napiling pinagmulan + Maghanap + Walang natagpuang resulta + Hindi natagpuan ang \'Ituloy ang Panonood\' + Walang natagpuang download + Hindi natagpuan ang file + Walang wastong episodyo na natagpuan + Piliin ang Tagapagbigay + Napili + Aklatan + Mga Bookmark + Ipinagpapatuloy ang %1$s... + Mode ng player + Advanced Player (hardware) + Simple Player (legacy) + diff --git a/app/src/main/res-car/values-b+tr/strings_car.xml b/app/src/main/res-car/values-b+tr/strings_car.xml new file mode 100644 index 00000000000..356b5e5df70 --- /dev/null +++ b/app/src/main/res-car/values-b+tr/strings_car.xml @@ -0,0 +1,54 @@ + + + Yükleniyor... + Hata + İçerik yüklenirken hata + Favoriler + Geçmiş + İndirilenler + Sağlayıcı + Mevcut + Menü + Ana Sayfa İçeriği + Sağlayıcıdan içerik yok + Sağlayıcı bulunamadı + Kaynak + Kaynaklar + Konu + Oyuncular + Detaylar yüklenirken hata + Bölüm Listesi + Sezon + Sezonlar + Bölüm + Bölüm bulunamadı + TV dizisi değil + Favorilere eklendi + Favorilerden çıkarıldı + Favori bulunamadı + Oynatma başlatılıyor... + Oynatılıyor + Bağlantı bulunamadı + Oynatılabilir içerik bulunamadı + Detaylar yüklenemedi + Bağlantılar yüklenirken hata + Ekrana Sığdır + Ekranı Doldur + Kaynak bulunamadı + Mevcut kaynak yok + Kaynak seçildi + Ara + Sonuç bulunamadı + \'İzlemeye Devam Et\' bulunamadı + İndirme bulunamadı + Dosya bulunamadı + Geçerli bölüm bulunamadı + Sağlayıcı Seç + Seçildi + Kütüphane + Yer İmleri + %1$s devam ettiriliyor... + Oynatıcı modu + Gelişmiş Oynatıcı (hardware) + Basit Oynatıcı (legacy) + diff --git a/app/src/main/res-car/values-b+uk/strings_car.xml b/app/src/main/res-car/values-b+uk/strings_car.xml new file mode 100644 index 00000000000..0c58383628b --- /dev/null +++ b/app/src/main/res-car/values-b+uk/strings_car.xml @@ -0,0 +1,54 @@ + + + Завантаження... + Помилка + Помилка завантаження вмісту + Обране + Історія + Завантаження + Провайдер + Поточний + Меню + Домашній вміст + Немає вмісту від провайдера + Провайдера не знайдено + Джерело + Джерела + Сюжет + Актори + Помилка завантаження деталей + Список епізодів + Сезон + Сезони + Епізод + Епізодів не знайдено + Не серіал + Додано до обраного + Видалено з обраного + Немає обраного + Початок відтворення... + Відтворення + Посилання не знайдено + Вміст для відтворення не знайдено + Не вдалося завантажити деталі + Помилка завантаження посилань + Припасувати до екрану + Заповнити екран + Джерело не знайдено + Джерела недоступні + Джерело вибрано + Пошук + Результатів не знайдено + \'Продовжити перегляд\' не знайдено + Завантажень не знайдено + Файл не знайдено + Дійсний епізод не знайдено + Вибрати провайдера + Вибрано + Бібліотека + Закладки + Відновлення %1$s... + Режим плеєра + Розширений плеєр (hardware) + Простий плеєр (legacy) + diff --git a/app/src/main/res-car/values-b+ur/strings_car.xml b/app/src/main/res-car/values-b+ur/strings_car.xml new file mode 100644 index 00000000000..eee9ee27167 --- /dev/null +++ b/app/src/main/res-car/values-b+ur/strings_car.xml @@ -0,0 +1,54 @@ + + + لوڈ ہو رہا ہے... + خرابی + مواد لوڈ کرنے میں خرابی + پسندیدہ + تاریخچہ + ڈاؤن لوڈز + فراہم کنندہ + موجودہ + مینو + ہوم مواد + فراہم کنندہ سے کوئی مواد نہیں + فراہم کنندہ نہیں ملا + ماخذ + ماخذات + پلاٹ + کاسٹ + تفصیلات لوڈ کرنے میں خرابی + اقساط کی فہرست + سیزن + سیزنز + قسط + کوئی قسط نہیں ملی + ٹی وی سیریز نہیں + پسندیدہ میں شامل کر دیا گیا + پسندیدہ سے ہٹا دیا گیا + کوئی پسندیدہ نہیں ملا + پلے بیک شروع ہو رہا ہے... + پلے ہو رہا ہے + کوئی لنک نہیں ملا + کوئی قابل پلے مواد نہیں ملا + تفصیلات لوڈ نہیں ہو سکیں + لنکس لوڈ کرنے میں خرابی + اسکرین پر فٹ کریں + اسکرین بھریں + کوئی ماخذ نہیں ملا + کوئی ماخذ دستیاب نہیں + ماخذ منتخب ہو گیا + تلاش + کوئی نتیجہ نہیں ملا + \'دیکھنا جاری رکھیں\' نہیں ملا + کوئی ڈاؤن لوڈ نہیں ملا + فائل نہیں ملی + کوئی درست قسط نہیں ملی + فراہم کنندہ منتخب کریں + منتخب شدہ + لائبریری + بک مارکس + %1$s دوبارہ شروع ہو رہا ہے... + پلیئر موڈ + ایڈوانسڈ پلیئر (hardware) + سادہ پلیئر (legacy) + diff --git a/app/src/main/res-car/values-b+uz/strings_car.xml b/app/src/main/res-car/values-b+uz/strings_car.xml new file mode 100644 index 00000000000..f8e9c2e3918 --- /dev/null +++ b/app/src/main/res-car/values-b+uz/strings_car.xml @@ -0,0 +1,54 @@ + + + Yuklanmoqda... + Xato + Kontentni yuklashda xato + Sevimlilar + Tarix + Yuklanmalar + Provayder + Joriy + Menyu + Bosh sahifa kontenti + Provayderdan kontent yo\'q + Provayder topilmadi + Manba + Manbalar + Syujet + Rollarda + Tafsilotlarni yuklashda xato + Qismlar ro\'yxati + Mavsum + Mavsumlar + Qism + Qismlar topilmadi + TV serial emas + Sevimlilarga qo\'shildi + Sevimlilardan olib tashlandi + Sevimlilar topilmadi + Ijro boshlanmoqda... + Ijro etilmoqda + Havola topilmadi + Ijro etib bo\'ladigan kontent topilmadi + Tafsilotlarni yuklab bo\'lmadi + Havolalarni yuklashda xato + Ekranga moslash + Ekranni to\'ldirish + Manba topilmadi + Mavjud manba yo\'q + Manba tanlandi + Qidirish + Natijalar topilmadi + \'Ko\'rishni davom ettirish\' topilmadi + Yuklanmalar topilmadi + Fayl topilmadi + Yaroqli qism topilmadi + Provayderni tanlash + Tanlandi + Kutubxona + Xatcho\'plar + %1$s davom ettirilmoqda... + Pleyer rejimi + Kengaytirilgan pleyer (hardware) + Oddiy pleyer (legacy) + diff --git a/app/src/main/res-car/values-b+vi/strings_car.xml b/app/src/main/res-car/values-b+vi/strings_car.xml new file mode 100644 index 00000000000..a02e1be99b5 --- /dev/null +++ b/app/src/main/res-car/values-b+vi/strings_car.xml @@ -0,0 +1,54 @@ + + + Đang tải... + Lỗi + Lỗi tải nội dung + Yêu thích + Lịch sử + Tải xuống + Nguồn phát + Hiện tại + Menu + Nội dung trang chủ + Không có nội dung từ nguồn + Không tìm thấy nguồn + Nguồn + Nguồn + Cốt truyện + Diễn viên + Lỗi tải chi tiết + Danh sách tập + Mùa + Mùa + Tập + Không tìm thấy tập nào + Không phải phim bộ + Đã thêm vào yêu thích + Đã xóa khỏi yêu thích + Không có yêu thích + Đang bắt đầu phát... + Đang phát + Không tìm thấy liên kết + Không có nội dung phát được + Không thể tải chi tiết + Lỗi tải liên kết + Vừa màn hình + Lấp đầy màn hình + Không tìm thấy nguồn + Không có nguồn khả dụng + Đã chọn nguồn + Tìm kiếm + Không tìm thấy kết quả + Không tìm thấy \'Tiếp tục xem\' + Không tìm thấy bản tải xuống + Không tìm thấy tệp + Không có tập hợp lệ + Chọn nguồn + Đã chọn + Thư viện + Dấu trang + Đang tiếp tục %1$s... + Chế độ trình phát + Trình phát nâng cao (hardware) + Trình phát đơn giản (legacy) + diff --git a/app/src/main/res-car/values-b+zh+TW/strings_car.xml b/app/src/main/res-car/values-b+zh+TW/strings_car.xml new file mode 100644 index 00000000000..40fbb565734 --- /dev/null +++ b/app/src/main/res-car/values-b+zh+TW/strings_car.xml @@ -0,0 +1,77 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + 播放器模式 + 進階播放器(hardware) + 簡易播放器(legacy) + diff --git a/app/src/main/res-car/values-b+zh/strings_car.xml b/app/src/main/res-car/values-b+zh/strings_car.xml new file mode 100644 index 00000000000..39bd3b14fde --- /dev/null +++ b/app/src/main/res-car/values-b+zh/strings_car.xml @@ -0,0 +1,54 @@ + + + 加载中... + 错误 + 加载内容错误 + 收藏 + 历史 + 下载 + 提供商 + 当前 + 菜单 + 首页内容 + 无提供商内容 + 未找到提供商 + 来源 + 来源 + 剧情 + 演员 + 加载详情错误 + 剧集列表 + + + + 未找到剧集 + 非电视剧 + 已添加到收藏 + 已从收藏移除 + 无收藏 + 开始播放... + 播放中 + 无链接 + 无由播放内容 + 无法加载详情 + 加载链接错误 + 适应屏幕 + 充满屏幕 + 未找到来源 + 无可用来源 + 已选来源 + 搜索 + 无结果 + 无继续观看 + 无下载 + 文件未找到 + 无有效剧集 + 选择提供商 + 已选 + + 书签 + 恢复播放 %1$s... + 播放器模式 + 高级播放器(hardware) + 简易播放器(legacy) + diff --git a/app/src/main/res-car/values-b+zu/strings_car.xml b/app/src/main/res-car/values-b+zu/strings_car.xml new file mode 100644 index 00000000000..969e45ea785 --- /dev/null +++ b/app/src/main/res-car/values-b+zu/strings_car.xml @@ -0,0 +1,54 @@ + + + Iyalayisha... + Iphutha + Iphutha ekulayisheni okuqukethwe + Izintandokazi + Umlando + Okulandiwe + Umhlinzeki + Okwamanje + Imenyu + Okuqukethwe Kwasekhaya + Akukho okuqukethwe okuvela kumhlinzeki + Umhlinzeki akatholakalanga + Umthombo + Imithombo + Icebo + Abalingisi + Iphutha ekulayisheni imininingwane + Uhlu Lweziqephu + Isizini + Izinkathi + Isiqephu + Azikho iziqephu ezitholiwe + Akulona uchungechunge lwe-TV + Kungezwe kuzintandokazi + Kususiwe kuzintandokazi + Azikho izintandokazi ezitholiwe + Iqala ukudlala... + Iyadlala + Asikho isixhumanisi esitholiwe + Akukho okuqukethwe okudlalekayo okutholiwe + Ayikwazi ukulayisha imininingwane + Iphutha ekulayisheni izixhumanisi + Lingana nesikrini + Gcwalisa Isikrini + Awukho umthombo otholiwe + Awukho umthombo otholakalayo + Umthombo ukhethiwe + Sesha + Ayikho imiphumela etholiwe + \'Qhubeka nokubuka\' ayitholakali + Akukho okulandiwe okutholiwe + Ifayela alitholakalanga + Asikho isiqephu esivumelekile esitholiwe + Khetha Umhlinzeki + Kukhethiwe + Umtapo wolwazi + Amabhukhimakhi + Iyaqhubeka %1$s... + Imodi yokudlala + Umdlali osezingeni eliphezulu (hardware) + Umdlali olula (legacy) + diff --git a/app/src/main/res-car/values-it/strings_car.xml b/app/src/main/res-car/values-it/strings_car.xml new file mode 100644 index 00000000000..be8f366e1de --- /dev/null +++ b/app/src/main/res-car/values-it/strings_car.xml @@ -0,0 +1,80 @@ + + + + Caricamento... + Errore + Errore caricamento contenuti + + + Preferiti + Cronologia + Download + Provider + Attuale + Menu + Contenuti Home + Nessun contenuto dal provider + Provider non trovato + Info su di me + + + Sorgente + Sorgenti + Trama + Cast + Errore caricamento dettagli + + + Lista Episodi + Stagione + Stagioni + Episodio + Nessun episodio trovato + Non è una serie TV + + + Aggiunto ai preferiti + Rimosso dai preferiti + Nessun preferito trovato + + + Avvio riproduzione... + In riproduzione + Nessun link trovato + Nessun contenuto riproducibile trovato + Impossibile caricare i dettagli + Errore caricamento link + Adatta allo schermo + Riempi schermo + + + Modalità player + Player avanzato (hardware) + Player semplice (legacy) + + + Nessuna sorgente trovata + Nessuna sorgente disponibile + Sorgente selezionata + + + Cerca + Nessun risultato trovato + + + Nessun elemento \'Continua a guardare\' trovato + + + Nessun download completato trovato + File non trovato + Nessun episodio valido trovato + + + Seleziona Provider + Selezionato + + + Libreria + Segnalibri + Riprendo %1$s... + diff --git a/app/src/main/res-car/values/strings_car.xml b/app/src/main/res-car/values/strings_car.xml new file mode 100644 index 00000000000..5b049bc3378 --- /dev/null +++ b/app/src/main/res-car/values/strings_car.xml @@ -0,0 +1,80 @@ + + + + Loading... + Error + Error loading content + + + Favorites + History + Downloads + Provider + Current + Menu + Home Content + No content from provider + Provider not found + About me + + + Source + Sources + Plot + Cast + Error loading details + + + Episode List + Season + Seasons + Episode + No episodes found + Not a TV series + + + Added to favorites + Removed from favorites + No favorites found + + + Starting playback... + Playing + No link found + No playable content found + Unable to load details + Error loading links + Fit to screen + Fill screen + + + Player mode + Advanced Player (hardware) + Simple Player (legacy) + + + No source found + No source available + Source selected + + + Search + No results found + + + No \'Continue watching\' items found + + + No completed downloads found + File not found + No valid episode found + + + Select Provider + Selected + + + Library + Bookmarks + Resuming %1$s... + diff --git a/app/src/main/res/drawable/go_back_30.xml b/app/src/main/res/drawable/go_back_30.xml index 14999011662..8202d3ce42c 100644 --- a/app/src/main/res/drawable/go_back_30.xml +++ b/app/src/main/res/drawable/go_back_30.xml @@ -1,7 +1,7 @@ diff --git a/app/src/main/res/drawable/ic_baseline_aspect_ratio_24.xml b/app/src/main/res/drawable/ic_baseline_aspect_ratio_24.xml index d93d1b64b91..c7b8b5f55fb 100644 --- a/app/src/main/res/drawable/ic_baseline_aspect_ratio_24.xml +++ b/app/src/main/res/drawable/ic_baseline_aspect_ratio_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml b/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml index f880379f0bb..0870be8f615 100644 --- a/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml +++ b/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/drawable/ic_baseline_source_24.xml b/app/src/main/res/drawable/ic_baseline_source_24.xml new file mode 100644 index 00000000000..0d7eb86d55f --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_source_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_tune_24.xml b/app/src/main/res/drawable/ic_baseline_tune_24.xml index d0c63a3d17d..0342fb06ea8 100644 --- a/app/src/main/res/drawable/ic_baseline_tune_24.xml +++ b/app/src/main/res/drawable/ic_baseline_tune_24.xml @@ -2,7 +2,7 @@ android:height="24dp" android:viewportWidth="48" android:viewportHeight="48" - android:tint="?attr/white" + android:tint="#FFFFFF" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/app/src/main/res/layout/remote_car_player_overlay.xml b/app/src/main/res/layout/remote_car_player_overlay.xml new file mode 100644 index 00000000000..fb49821e544 --- /dev/null +++ b/app/src/main/res/layout/remote_car_player_overlay.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/raw/aboutme.mp4 b/app/src/main/res/raw/aboutme.mp4 new file mode 100644 index 00000000000..cfb4479ea70 Binary files /dev/null and b/app/src/main/res/raw/aboutme.mp4 differ diff --git a/app/src/main/res/values-b+es/array.xml b/app/src/main/res/values-es/array.xml similarity index 100% rename from app/src/main/res/values-b+es/array.xml rename to app/src/main/res/values-es/array.xml diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-es/strings.xml similarity index 100% rename from app/src/main/res/values-b+es/strings.xml rename to app/src/main/res/values-es/strings.xml diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-it/strings.xml similarity index 100% rename from app/src/main/res/values-b+it/strings.xml rename to app/src/main/res/values-it/strings.xml diff --git a/app/src/main/res/xml/automotive_app_desc.xml b/app/src/main/res/xml/automotive_app_desc.xml new file mode 100644 index 00000000000..c601d28ff79 --- /dev/null +++ b/app/src/main/res/xml/automotive_app_desc.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d32aad375ef..37267e06f36 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,7 @@ androidGradlePlugin = "8.13.2" appcompat = "1.7.1" biometric = "1.4.0-alpha04" buildkonfigGradlePlugin = "0.17.1" +carApp = "1.8.0-alpha03" coil = "3.3.0" colorpicker = "6b46b49bd5" conscryptAndroid = { strictly = "2.5.2" } # 2.5.3 crashes everything @@ -49,7 +50,7 @@ zipline = "1.24.0" jvmTarget = "1.8" jdkToolchain = "17" -minSdk = "21" +minSdk = "23" compileSdk = "36" targetSdk = "36" @@ -57,6 +58,7 @@ targetSdk = "36" activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" } +car-app = { module = "androidx.car.app:app", version.ref = "carApp" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } colorpicker = { module = "com.github.recloudstream:color-picker-android", version.ref = "colorpicker" }