diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0feb866be..2ad8451a3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -276,6 +276,8 @@ dependencies { implementation(libs.jackson.module.kotlin) // JSON Parser implementation(libs.zipline) + // Temp/deprecated; will be removed once extensions have time to migrate from using it + implementation("com.google.code.gson:gson:2.11.0") // Deprecated; will be removed once extensions have time to migrate from using it implementation("me.xdrop:fuzzywuzzy:1.4.0") diff --git a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt index a9cd9c01e..466ef5a00 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt @@ -113,7 +113,7 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory { get() = _context?.get() private set(value) { _context = WeakReference(value) - setContext(WeakReference(value)) + setContext(value) } fun getKeyClass(path: String, valueType: Class): T? { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt index 0d35d5670..19c006b28 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt @@ -10,6 +10,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.api.Log +import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.isEpisodeBased import com.lagradost.cloudstream3.mvvm.Resource @@ -36,6 +37,7 @@ import com.lagradost.cloudstream3.utils.ResourceLiveData import com.lagradost.cloudstream3.utils.downloader.DownloadObjects import com.lagradost.cloudstream3.utils.downloader.DownloadQueueManager import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager.deleteFilesAndUpdateSettings +import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager.downloadDeleteEvent import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager.getDownloadFileInfo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -67,6 +69,17 @@ class DownloadViewModel : ViewModel() { private val _selectedItemIds = ConsistentLiveData?>(null) val selectedItemIds: LiveData?> = _selectedItemIds + init { + // Keep the Downloads list in sync when a download is deleted/cancelled from + // anywhere in the app (result page button, queue, notification, etc.). See + // onDownloadDeleted for the rationale (issue #1227). + downloadDeleteEvent += ::onDownloadDeleted + } + + override fun onCleared() { + downloadDeleteEvent -= ::onDownloadDeleted + super.onCleared() + } fun cancelSelection() { updateSelectedItems { null } @@ -389,6 +402,18 @@ class DownloadViewModel : ViewModel() { postChildren(_childCards.success?.filter { it.data.id !in idsToRemove }) } + /** + * Refreshes the Downloads screen in real time when a download is deleted/cancelled. + */ + private fun onDownloadDeleted(id: Int) { + // Keep multi-select state consistent: forget the removed id if it was selected. + updateSelectedItems { it?.minus(id) } + + val context = CloudStreamApp.context ?: return + updateHeaderList(context) + postChildren(_childCards.success?.filterNot { it.data.id == id }) + } + private fun updateStorageStats(visual: List) { try { val stat = StatFs(Environment.getExternalStorageDirectory().path) @@ -584,4 +609,4 @@ class DownloadViewModel : ViewModel() { val names: List, val parentName: String? ) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt index 382a770cd..450dc9a61 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt @@ -164,9 +164,11 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : } } - /*fun downloadDeleteEvent(data: Int) { - - }*/ + fun downloadDeleteEvent(data: Int) { + if (data == persistentId) { + resetView() + } + } /*fun downloadEvent(data: Pair) { val (id, action) = data @@ -185,7 +187,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : override fun onAttachedToWindow() { VideoDownloadManager.downloadStatusEvent += ::downloadStatusEvent - // VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent + VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent // VideoDownloadManager.downloadEvent += ::downloadEvent VideoDownloadManager.downloadProgressEvent += ::downloadProgressEvent @@ -200,7 +202,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : override fun onDetachedFromWindow() { VideoDownloadManager.downloadStatusEvent -= ::downloadStatusEvent - // VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent + VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent // VideoDownloadManager.downloadEvent -= ::downloadEvent VideoDownloadManager.downloadProgressEvent -= ::downloadProgressEvent 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 ab0715c6d..0e52baff6 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 @@ -24,6 +24,8 @@ import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.plugins.PluginManager +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.chip.Chip @@ -43,6 +45,7 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable +import com.lagradost.cloudstream3.plugins.Plugin import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi import com.lagradost.cloudstream3.ui.BaseFragment @@ -424,11 +427,30 @@ class HomeFragment : BaseFragment( .inflate(R.layout.sort_bottom_single_provider_choice, parent, false) val titleText = view.findViewById(R.id.text1) val pinIcon = view.findViewById(R.id.pinicon) + val settingsIcon = view.findViewById(R.id.action_settings) + val name = getItem(position) titleText?.text = name + val providerApi = currentValidApis[position] val isPinned = - pinnedphashset.contains(currentValidApis[position].name) + pinnedphashset.contains(providerApi.name) pinIcon.visibility = if (isPinned) View.VISIBLE else View.GONE + + val pluginInstance = providerApi.sourcePlugin?.let { PluginManager.plugins[it] } as? Plugin + val isDownloadedPluginWithSettings = pluginInstance?.openSettings != null && !isLayout(TV) + + settingsIcon.visibility = if (isDownloadedPluginWithSettings) View.VISIBLE else View.GONE + if (isDownloadedPluginWithSettings) { + settingsIcon.setOnClickListener { + try { + val activityContext = it.context.getActivity() ?: it.context + pluginInstance.openSettings?.invoke(activityContext) + } catch (e: Throwable) { + logError(e) + } + } + } + return view } } @@ -451,7 +473,14 @@ class HomeFragment : BaseFragment( arrayAdapter.clear() val sortedApis = validAPIs .filter { - it.hasMainPage && (pinnedphashset.contains(it.name) || it.supportedTypes.any( + val isPinned = pinnedphashset.contains(it.name) + + // Hide pinned NSFW when NSFW not selected. NSFW is distracting when not chosen. + if (isPinned && !preSelectedTypes.contains(TvType.NSFW)) { + if (it.supportedTypes.all { type -> type == TvType.NSFW }) return@filter false + } + + it.hasMainPage && (isPinned || it.supportedTypes.any( preSelectedTypes::contains )) } @@ -672,6 +701,23 @@ class HomeFragment : BaseFragment( homeViewModel.queryTextSubmit("") } + // Load value for toggling Tv layout real time clock. Hide by default at startup + // set visibility first, to apply a scroll effect later + context?.let { + if (isLayout(TV)) { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(it) + val toggleClock = + settingsManager.getBoolean( + getString(R.string.tv_layout_clock_key), + false + ) + binding.homeClock.isVisible = toggleClock + } else { + binding.homeClock.isVisible = false + } + } + + homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (isLayout(PHONE)) { @@ -711,6 +757,17 @@ class HomeFragment : BaseFragment( view.getLocationInWindow(rect) scrollParent.isVisible = true scrollParent.translationY = rect[1].toFloat() - 60.toPx + + // Move the TV layout real time clock out of the way too + // We check if we have the correct layout and if the clock is enabled + if(isLayout(TV) && binding.homeClock.isVisible) { + val scrollParent = binding.homeClock + + val rect = IntArray(2) + view.getLocationInWindow(rect) + scrollParent.isVisible = true + scrollParent.translationY = rect[1].toFloat() - 60.toPx + } } } super.onScrolled(recyclerView, dx, dy) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index e42e774b5..1020205ac 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -58,7 +58,7 @@ class HomeScrollAdapter( when (binding) { is HomeScrollViewBinding -> { - binding.homeScrollPreview.loadImage(posterUrl) + binding.homeScrollPreview.loadImage(posterUrl, item.posterHeaders) binding.homeScrollPreviewTags.apply { text = item.tags?.joinToString(" • ") ?: "" isGone = item.tags.isNullOrEmpty() @@ -79,8 +79,8 @@ class HomeScrollAdapter( binding.homeScrollPreview.setOnClickListener { view -> callback.invoke(view ?: return@setOnClickListener, position, item) } - binding.homeScrollPreview.loadImage(posterUrl) + binding.homeScrollPreview.loadImage(posterUrl, item.posterHeaders) } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 20c37dcf4..d90b6043f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -307,6 +307,7 @@ open class FullScreenPlayer : AbstractPlayerFragment( playerVideoTitleRez, playerVideoInfo, playerGoBackHolder, + playerVideoClock, ).forEach { it.animateY(titleMove) } @@ -772,7 +773,7 @@ open class FullScreenPlayer : AbstractPlayerFragment( val showPlayerEpisodes = !isGone && isThereEpisodes() playerEpisodesButtonRoot.isVisible = showPlayerEpisodes playerEpisodesButton.isVisible = showPlayerEpisodes - playerVideoTitleHolder.isGone = togglePlayerTitleGone + playerVideoTitleHolder.isGone = togglePlayerTitleGone || playerVideoTitle.text.isBlank() playerVideoTitleRez.isGone = isGone || playerVideoTitleRez.text.isBlank() playerEpisodeFiller.isGone = isGone playerCenterMenu.isGone = isGone diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 91e3ff970..4495a5602 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -8,6 +8,7 @@ import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.graphics.Bitmap +import android.graphics.Typeface import android.os.Build import android.os.Bundle import android.text.Spanned @@ -79,6 +80,7 @@ import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioT import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.updateForcedEncoding import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType import com.lagradost.cloudstream3.ui.player.source_priority.LinkSource +import com.lagradost.cloudstream3.ui.player.source_priority.ProfileSettings import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog @@ -132,6 +134,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import java.io.Serializable +import java.lang.ref.WeakReference import java.util.Calendar import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -510,7 +513,7 @@ class GeneratorPlayer : FullScreenPlayer() { showDownloadProgress(DownloadEvent(0, 0, 0, null)) // uiReset() // Removed due to UX - + currentSelectedLink = link // setEpisodes(viewModel.getAllMeta() ?: emptyList()) setPlayerDimen(null) @@ -1112,21 +1115,29 @@ class GeneratorPlayer : FullScreenPlayer() { var sourceIndex = 0 var startSource = 0 - var sortedUrls = emptyList>() + // Filtered and sorted links + var currentHiddenFooter: View? = null + var filteredLinks: List = emptyList() fun refreshLinks(qualityProfile: Int) { - sortedUrls = viewModel.state.sortLinks(qualityProfile) - if (sortedUrls.isEmpty()) { + val currentLinkUsed = currentSelectedLink + // Always display current linkFooter + val sortedLinks = viewModel.state.sortLinks(qualityProfile) + + filteredLinks = sortedLinks.filter { it.shouldUseLink || it.link == currentLinkUsed } + + if (sortedLinks.isEmpty()) { sourceDialog.findViewById(R.id.sort_sources_holder)?.isGone = true } else { - startSource = sortedUrls.indexOf(currentSelectedLink) + startSource = filteredLinks.indexOfFirst { it.link == currentLinkUsed } sourceIndex = startSource val sourcesArrayAdapter = ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) - sourcesArrayAdapter.addAll(sortedUrls.map { (link, uri) -> + sourcesArrayAdapter.addAll(filteredLinks.map { displayLink -> + val (link, uri) = displayLink.link val name = link?.name ?: uri?.name ?: "NULL" "$name ${Qualities.getStringByInt(link?.quality)}" }) @@ -1142,7 +1153,7 @@ class GeneratorPlayer : FullScreenPlayer() { } providerList.setOnItemLongClickListener { _, _, position, _ -> - sortedUrls.getOrNull(position)?.first?.url?.let { + sortedLinks.getOrNull(position)?.link?.first?.url?.let { clipboardHelper( txt(R.string.video_source), it @@ -1150,6 +1161,25 @@ class GeneratorPlayer : FullScreenPlayer() { } true } + + val hiddenLinks = sortedLinks.size - filteredLinks.size + providerList.removeFooterView(currentHiddenFooter) + + if (hiddenLinks > 0) { + val hiddenLinksFooter: TextView = layoutInflater.inflate( + R.layout.sort_bottom_footer_add_choice, null + ) as TextView + + providerList.addFooterView(hiddenLinksFooter, null, false) + currentHiddenFooter = hiddenLinksFooter + + val hiddenLinksText = + ctx.resources.getQuantityString(R.plurals.links_hidden, hiddenLinks) + .format(hiddenLinks) + hiddenLinksFooter.text = hiddenLinksText + hiddenLinksFooter.setCompoundDrawables(null, null, null, null) + hiddenLinksFooter.setTypeface(null, Typeface.ITALIC) + } } } @@ -1363,8 +1393,8 @@ class GeneratorPlayer : FullScreenPlayer() { } } if (init) { - sortedUrls.getOrNull(sourceIndex)?.let { - loadLink(it, true) + filteredLinks.getOrNull(sourceIndex)?.let { + loadLink(it.link, true) } } sourceDialog.dismissSafe(activity) @@ -1532,6 +1562,10 @@ class GeneratorPlayer : FullScreenPlayer() { } override fun playerError(exception: Throwable) { + currentSelectedLink?.let { link -> + viewModel.modifyState { this.addError(link) } + } + val currentUrl = currentSelectedLink?.let { it.first?.url ?: it.second?.uri?.toString() } ?: "unknown" val headers = currentSelectedLink?.first?.headers?.toString() ?: "none" @@ -1554,8 +1588,22 @@ class GeneratorPlayer : FullScreenPlayer() { private fun noLinksFound() { viewModel.forceClearCache = true + val hiddenLinks = viewModel.state.sortLinks(currentQualityProfile).count { !it.shouldUseLink } + + context?.let { ctx -> + // Display that there are hidden links to the user. + if (hiddenLinks > 0) { + val noLinksString = ctx.getString(R.string.no_links_found_toast) + val hiddenString = + ctx.resources.getQuantityString(R.plurals.links_hidden, hiddenLinks) + .format(hiddenLinks) + val toastText = "$noLinksString\n($hiddenString)" + showToast(toastText, Toast.LENGTH_SHORT) + } else { + showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT) + } + } - showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT) activity?.popCurrentPage() } @@ -1566,7 +1614,9 @@ class GeneratorPlayer : FullScreenPlayer() { } val links = viewModel.state.sortLinks(currentQualityProfile) - if (links.isEmpty()) { + + val firstAvailableLink = links.firstOrNull { it.shouldUseLink }?.link + if (firstAvailableLink == null) { noLinksFound() return } @@ -1574,7 +1624,7 @@ class GeneratorPlayer : FullScreenPlayer() { if (!isPlayerActive.compareAndSet(false, true)) { return } - loadLink(links.first(), false) + loadLink(firstAvailableLink, false) showPlayerMetadata() } @@ -1641,25 +1691,26 @@ class GeneratorPlayer : FullScreenPlayer() { } } - override fun hasNextMirror(): Boolean { + private fun getNextLink(): DisplayLink? { val links = viewModel.state.sortLinks(currentQualityProfile) - return links.isNotEmpty() && links.indexOf(currentSelectedLink) + 1 < links.size + val currentIndex = links.indexOfFirst { it.link == currentSelectedLink } + val nextPotentialLink = + links.withIndex().firstOrNull { it.index > currentIndex && it.value.shouldUseLink } + return nextPotentialLink?.value + } + + override fun hasNextMirror(): Boolean { + return getNextLink() != null } override fun nextMirror() { - val links = viewModel.state.sortLinks(currentQualityProfile) - if (links.isEmpty()) { + val nextLink = getNextLink() + if (nextLink == null) { noLinksFound() return } - val newIndex = links.indexOf(currentSelectedLink) + 1 - if (newIndex >= links.size) { - noLinksFound() - return - } - - loadLink(links[newIndex], true) + loadLink(nextLink.link, true) } override fun onDestroy() { @@ -2166,6 +2217,7 @@ class GeneratorPlayer : FullScreenPlayer() { isPlayerActive.set(false) binding?.overlayLoadingSkipButton?.isVisible = false binding?.playerLoadingOverlay?.isVisible = true + viewModel.modifyState { setError(emptyList()) } uiReset() } @@ -2219,6 +2271,14 @@ class GeneratorPlayer : FullScreenPlayer() { fromTagToEnglishLanguageName(it)?.lowercase() ?: return@mapNotNull null } ?: listOf() } + + // Set up TV clock visibility + if (isLayout(TV)) { + val showTvClock = settingsManager.getBoolean(ctx.getString(R.string.tv_layout_clock_key), false) + playerBinding?.playerVideoClock?.isVisible = showTvClock + } else { + playerBinding?.playerVideoClock?.isVisible = false + } } unwrapBundle(savedInstanceState) @@ -2297,19 +2357,23 @@ class GeneratorPlayer : FullScreenPlayer() { } } - observe(viewModel.currentLinks) { (links, instance) -> + observe(viewModel.currentLinks) { (_, instance) -> if (instance != viewModel.state.instance) return@observe // Outdated observe - val turnVisible = links.isNotEmpty() && viewModel.generator?.canSkipLoading == true + val sortedLinks = viewModel.state.sortLinks(currentQualityProfile) + val usableLinks = sortedLinks.count { link -> link.shouldUseLink } + + val turnVisible = usableLinks > 0 && viewModel.generator?.canSkipLoading == true val wasGone = binding.overlayLoadingSkipButton.isGone binding.overlayLoadingSkipButton.apply { isVisible = turnVisible - if (links.isEmpty()) { + + if (usableLinks == 0) { setText(R.string.skip_loading) } else { @SuppressLint("SetTextI18n") - text = "${context.getString(R.string.skip_loading)} (${links.size})" + text = "${context.getString(R.string.skip_loading)} (${usableLinks})" } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index e3c390d50..cb8cf8bff 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -10,6 +10,8 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.ui.player.source_priority.ProfileSettings +import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.Coroutines.ioSafe @@ -40,12 +42,20 @@ data class GeneratorState( val id: Int?, ) +data class DisplayLink( + val link: VideoLink, + // If the link should be displayed and used by the player + val shouldUseLink: Boolean, + val priority: Int +) + /** Immutable state of all current links relevant to displaying the video */ // @MustUseReturnValues // @Immutable data class VideoState( val subtitles: PersistentSet = persistentSetOf(), val links: PersistentSet = persistentSetOf(), + val erroredLinks: PersistentSet = persistentSetOf(), val stamps: PersistentList = persistentListOf(), val loading: Resource = Resource.Loading(), val generatorState: GeneratorState? = null, @@ -56,19 +66,52 @@ data class VideoState( * * sortedBy is not exactly expensive, but each hasNextMirror does it again, so this alleviates unnecessary recomputation * */ - private val sortedLinks: ConcurrentHashMap> = ConcurrentHashMap() + private val sortedLinks: ConcurrentHashMap> = ConcurrentHashMap() + /** + * The cache is guaranteed to be up to date link-wise due to the immutable links. + * However, hideNegativeSources and hideErrorSources could be updated, which requires clearing the cache. + */ fun clearSortedLinksCache() = sortedLinks.clear() + private fun hasLinkErrored(link: VideoLink): Boolean { + return erroredLinks.any { it == link } + } + + private fun VideoLink.toDisplayLink( + qualityProfile: Int, + hideNegativeSources: Boolean, + hideErrorSources: Boolean + ): DisplayLink { + val priority = getLinkPriority(qualityProfile, this.first) + val shouldHideLink = + (hideNegativeSources && priority < 0) || (hideErrorSources && hasLinkErrored(this)) + val displayLink = DisplayLink(this, !shouldHideLink, priority) + + return displayLink + } + // Modifying sortedLinks is not considered a "visible" side effect, and rerunning it does not change the result // It is by all standards, idempotent and by extension also pure as it has no "visible" side effect /** Returns .links in the sorted order according to the qualityProfile. * Use .links if order is not needed */ @Contract(pure = true) - fun sortLinks(qualityProfile: Int): List { - return sortedLinks[qualityProfile] ?: links.sortedBy { link -> + fun sortLinks(qualityProfile: Int): List { + sortedLinks[qualityProfile]?.let { + return it + } + + val hideNegativeSources = + QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideNegativeSources) + val hideErrorSources = + QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideErrorSources) + + return links.map { link -> // negative because we want to sort highest quality first - -getLinkPriority(qualityProfile, link.first) + link.toDisplayLink(qualityProfile, hideNegativeSources, hideErrorSources) + }.sortedBy { + // negative because we want to sort highest quality first + -it.priority }.also { value -> sortedLinks[qualityProfile] = value } } @@ -113,6 +156,12 @@ data class VideoState( @JvmName("setVideoSkipStamp") @Contract(pure = true) fun set(items: Collection): VideoState = copy(stamps = items.toPersistentList()) + + @Contract(pure = true) + fun addError(item: VideoLink): VideoState = copy(erroredLinks = erroredLinks.add(item)) + + @Contract(pure = true) + fun setError(items: Collection): VideoState = copy(erroredLinks = items.toPersistentSet()) } data class VideoLive( @@ -141,9 +190,8 @@ class PlayerGeneratorViewModel : ViewModel() { var state = VideoState(instance = 0) private set - private val _currentLinks = - MutableLiveData>>>(null) - val currentLinks: LiveData>>> = _currentLinks + private val _currentLinks = MutableLiveData>>(null) + val currentLinks: LiveData>> = _currentLinks private val _currentSubtitles = MutableLiveData>>(null) val currentSubtitles: LiveData>> = _currentSubtitles diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt index 15d25148c..d8d47bd10 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt @@ -12,12 +12,16 @@ import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities +import java.util.EnumMap +import java.util.concurrent.ConcurrentHashMap +import kotlin.also import kotlin.math.abs object QualityDataHelper { private const val VIDEO_SOURCE_PRIORITY = "video_source_priority" private const val VIDEO_PROFILE_NAME = "video_profile_name" private const val VIDEO_QUALITY_PRIORITY = "video_quality_priority" + const val VIDEO_PROFILE_SETTINGS = "video_profile_settings" // Old key only supporting one type per profile @Deprecated("Changed to support multiple types per profile") @@ -53,13 +57,21 @@ object QualityDataHelper { val types: Set ) + + // Map profile and name to priority + val sourcePriorityCache: ConcurrentHashMap> = ConcurrentHashMap() + fun getSourcePriority(profile: Int, name: String?): Int { if (name == null) return DEFAULT_SOURCE_PRIORITY - return getKey( + + return sourcePriorityCache[profile]?.get(name) ?: (getKey( "$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile", name, DEFAULT_SOURCE_PRIORITY - ) ?: DEFAULT_SOURCE_PRIORITY + ) ?: DEFAULT_SOURCE_PRIORITY).also { + sourcePriorityCache.getOrPut(profile) { hashMapOf() } + sourcePriorityCache[profile]?.set(name, it) + } } fun getAllSourcePriorityNames(profile: Int): List { @@ -77,6 +89,8 @@ object QualityDataHelper { } else { setKey(folder, name, priority) } + + sourcePriorityCache[profile]?.set(name, priority) } fun setProfileName(profile: Int, name: String?) { @@ -93,12 +107,17 @@ object QualityDataHelper { ?: txt(R.string.profile_number, profile) } + // Map profile and quality to priority + val qualityPriorityCache: ConcurrentHashMap> = ConcurrentHashMap() fun getQualityPriority(profile: Int, quality: Qualities): Int { - return getKey( + return qualityPriorityCache[profile]?.get(quality) ?: (getKey( "$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile", quality.value.toString(), quality.defaultPriority - ) ?: quality.defaultPriority + )?.also { + qualityPriorityCache.getOrPut(profile) { EnumMap(Qualities::class.java) } + qualityPriorityCache[profile]?.set(quality, it) + }) ?: quality.defaultPriority } fun setQualityPriority(profile: Int, quality: Qualities, priority: Int) { @@ -107,8 +126,24 @@ object QualityDataHelper { quality.value.toString(), priority ) + qualityPriorityCache[profile]?.set(quality, priority) } + fun setProfileSetting(profile: Int, setting: ProfileSettings, value: T) { + val folder = "$currentAccount/$VIDEO_PROFILE_SETTINGS/$profile" + // Prevent unnecessary keys + if (value == setting.defaultValue) { + removeKey(folder, setting.key) + } else { + setKey(folder, setting.key, value) + } + } + + inline fun getProfileSetting(profile: Int, setting: ProfileSettings): T { + val folder = "$currentAccount/$VIDEO_PROFILE_SETTINGS/$profile" + val value = getKey(folder, setting.key) + return value ?: setting.defaultValue + } @Suppress("DEPRECATION") fun getQualityProfileTypes(profile: Int): Set { @@ -224,3 +259,8 @@ object QualityDataHelper { return Qualities.entries.minBy { abs(it.value - target) } } } + +sealed class ProfileSettings(val key: String, val defaultValue: T) { + object HideErrorSources : ProfileSettings("hide_error_sources", false) + object HideNegativeSources : ProfileSettings("hide_negative_sources", false) +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt index c8ac96ebb..604ec3149 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt @@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding class SourcePriorityDialog( val ctx: Context, - @StyleRes themeRes: Int, + @StyleRes val themeRes: Int, val links: List, private val profile: QualityDataHelper.QualityProfile, /** @@ -28,76 +28,79 @@ class SourcePriorityDialog( PlayerSelectSourcePriorityBinding.inflate(LayoutInflater.from(ctx), null, false) setContentView(binding.root) fixSystemBarsPadding(binding.root) - val sourcesRecyclerView = binding.sortSources - val qualitiesRecyclerView = binding.sortQualities - val profileText = binding.profileTextEditable - val saveBtt = binding.saveBtt - val exitBtt = binding.closeBtt - val helpBtt = binding.helpBtt - profileText.setText(QualityDataHelper.getProfileName(profile.id).asString(context)) - profileText.hint = txt(R.string.profile_number, profile.id).asString(context) + binding.apply { + profileTextEditable.setText( + QualityDataHelper.getProfileName(profile.id).asString(context) + ) + profileTextEditable.hint = txt(R.string.profile_number, profile.id).asString(context) - sourcesRecyclerView.adapter = PriorityAdapter( - ).apply { - submitList(links.map { link -> - SourcePriority( - null, - link.source, - QualityDataHelper.getSourcePriority(profile.id, link.source) - ) - }.distinctBy { it.name }.sortedBy { -it.priority }) - } + sortSources.adapter = PriorityAdapter( + ).apply { + val sortedLinks = links.map { link -> + SourcePriority( + null, + link.source, + QualityDataHelper.getSourcePriority(profile.id, link.source) + ) + }.distinctBy { it.name }.sortedBy { -it.priority } - qualitiesRecyclerView.adapter = PriorityAdapter( - ).apply { - submitList(Qualities.entries.mapNotNull { - SourcePriority( - it, - Qualities.getStringByIntFull(it.value).ifBlank { return@mapNotNull null }, - QualityDataHelper.getQualityPriority(profile.id, it) - ) - }.sortedBy { -it.priority }) - } - - @Suppress("UNCHECKED_CAST") // We know the types - saveBtt.setOnClickListener { - val qualityAdapter = qualitiesRecyclerView.adapter as? PriorityAdapter - val sourcesAdapter = sourcesRecyclerView.adapter as? PriorityAdapter - - val qualities = qualityAdapter?.immutableCurrentList ?: emptyList() - val sources = sourcesAdapter?.immutableCurrentList ?: emptyList() - - qualities.forEach { - QualityDataHelper.setQualityPriority(profile.id, it.data, it.priority) + submitList(sortedLinks) } - sources.forEach { - QualityDataHelper.setSourcePriority(profile.id, it.name, it.priority) + sortQualities.adapter = PriorityAdapter( + ).apply { + submitList(Qualities.entries.mapNotNull { + SourcePriority( + it, + Qualities.getStringByIntFull(it.value).ifBlank { return@mapNotNull null }, + QualityDataHelper.getQualityPriority(profile.id, it) + ) + }.sortedBy { -it.priority }) } - qualityAdapter?.submitList(qualities.sortedBy { -it.priority }) - sourcesAdapter?.submitList(sources.sortedBy { -it.priority }) + @Suppress("UNCHECKED_CAST") // We know the types + saveBtt.setOnClickListener { + val qualityAdapter = sortQualities.adapter as? PriorityAdapter + val sourcesAdapter = sortSources.adapter as? PriorityAdapter - val savedProfileName = profileText.text.toString() - if (savedProfileName.isBlank()) { - QualityDataHelper.setProfileName(profile.id, null) - } else { - QualityDataHelper.setProfileName(profile.id, savedProfileName) + val qualities = qualityAdapter?.immutableCurrentList ?: emptyList() + val sources = sourcesAdapter?.immutableCurrentList ?: emptyList() + + qualities.forEach { + QualityDataHelper.setQualityPriority(profile.id, it.data, it.priority) + } + + sources.forEach { + QualityDataHelper.setSourcePriority(profile.id, it.name, it.priority) + } + + qualityAdapter?.submitList(qualities.sortedBy { -it.priority }) + sourcesAdapter?.submitList(sources.sortedBy { -it.priority }) + + val savedProfileName = profileTextEditable.text.toString() + if (savedProfileName.isBlank()) { + QualityDataHelper.setProfileName(profile.id, null) + } else { + QualityDataHelper.setProfileName(profile.id, savedProfileName) + } + updatedCallback.invoke() } - updatedCallback.invoke() - } - exitBtt.setOnClickListener { - this.dismissSafe() - } + closeBtt.setOnClickListener { + dismissSafe() + } - helpBtt.setOnClickListener { - AlertDialog.Builder(context, R.style.AlertDialogCustom).apply { - setMessage(R.string.quality_profile_help) - }.show() - } + helpBtt.setOnClickListener { + AlertDialog.Builder(context, R.style.AlertDialogCustom).apply { + setMessage(R.string.quality_profile_help) + }.show() + } + settingsBtt.setOnClickListener { + SourceProfileSettingsDialog(ctx, themeRes, profile.id).show() + } + } super.show() } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourceProfileSettingsDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourceProfileSettingsDialog.kt new file mode 100644 index 000000000..c26955b5e --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourceProfileSettingsDialog.kt @@ -0,0 +1,48 @@ +package com.lagradost.cloudstream3.ui.player.source_priority + +import android.app.Dialog +import android.content.Context +import android.view.LayoutInflater +import androidx.annotation.StyleRes +import com.lagradost.cloudstream3.databinding.SourceProfileSettingsDialogBinding +import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe +import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding + +class SourceProfileSettingsDialog( + val ctx: Context, + @StyleRes themeRes: Int, + val profile: Int +) : Dialog(ctx, themeRes) { + override fun show() { + val binding = + SourceProfileSettingsDialogBinding.inflate(LayoutInflater.from(ctx), null, false) + setContentView(binding.root) + fixSystemBarsPadding(binding.root) + + binding.apply { + var hideErrorSources = QualityDataHelper.getProfileSetting(profile, ProfileSettings.HideErrorSources) + var hideNegativeSources = QualityDataHelper.getProfileSetting(profile, ProfileSettings.HideNegativeSources) + + profileHideErrorSources.isChecked = hideErrorSources + profileHideErrorSources.setOnCheckedChangeListener { _, bool -> + hideErrorSources = bool + } + + profileHideNegativeSources.isChecked = hideNegativeSources + profileHideNegativeSources.setOnCheckedChangeListener { _, bool -> + hideNegativeSources = bool + } + + applyBtt.setOnClickListener { + QualityDataHelper.setProfileSetting(profile, ProfileSettings.HideErrorSources, hideErrorSources) + QualityDataHelper.setProfileSetting(profile, ProfileSettings.HideNegativeSources, hideNegativeSources) + dismissSafe() + } + + cancelBtt.setOnClickListener { + dismissSafe() + } + } + super.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index cfbacc5d1..ce67763d0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -877,7 +877,7 @@ class ResultFragmentTv : BaseFragment( resultCastText.setText(d.actorsText) resultNextAiring.setText(d.nextAiringEpisode) resultNextAiringTime.setText(d.nextAiringDate) - resultPoster.loadImage(d.posterImage) + resultPoster.loadImage(d.posterImage, headers = d.posterHeaders) var isExpanded = false resultDescription.apply { @@ -910,7 +910,7 @@ class ResultFragmentTv : BaseFragment( R.drawable.profile_bg_teal ).random() - backgroundPoster.loadImage(d.posterBackgroundImage) { + backgroundPoster.loadImage(d.posterBackgroundImage, headers = d.posterHeaders) { error { getImageFromDrawable(context ?: return@error null, error) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index 354424978..b04f9a4a2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -77,6 +77,7 @@ val appLanguages = arrayListOf( Pair("Azərbaycan dili", "az"), Pair("Bahasa Indonesia", "in"), Pair("Bahasa Melayu", "ms"), + Pair("català", "ca"), Pair("Deutsch", "de"), Pair("English", "en"), Pair("Español", "es"), @@ -97,6 +98,7 @@ val appLanguages = arrayListOf( Pair("Português", "pt"), Pair("Português (Brasil)", "pt-BR"), Pair("Română", "ro"), + Pair("Shqip мова", "sq"), Pair("Slovenčina", "sk"), Pair("Soomaaliga", "so"), Pair("Svenska", "sv"), @@ -106,6 +108,7 @@ val appLanguages = arrayListOf( Pair("Wikang Filipino", "fil"), Pair("Čeština", "cs"), Pair("Ελληνικά", "el"), + Pair("беларуская мова", "be"), Pair("български", "bg"), Pair("македонски", "mk"), Pair("русский", "ru"), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index f4c522bf9..5f86a3a5a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -228,6 +228,8 @@ class SettingsUI : BasePreferenceFragmentCompat() { return@setOnPreferenceClickListener true } + getPref(R.string.tv_layout_clock_key)?.hideOn(PHONE or EMULATOR) + getPref(R.string.confirm_exit_key)?.setOnPreferenceClickListener { val prefNames = resources.getStringArray(R.array.confirm_exit) val prefValues = resources.getIntArray(R.array.confirm_exit_values) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt index cf892424f..bf77ef247 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt @@ -115,6 +115,9 @@ class SubtitlesFragment : BaseDialogFragment( ) } + view.clipToPadding = false + view.clipChildren = false + // we default to 25sp, this is needed as RoundedBackgroundColorSpan breaks on override sizes val size = data.fixedTextSize ?: 25.0f view.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, size) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt index 02ee69791..68f262d8d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt @@ -181,11 +181,11 @@ object DataStore { } fun Context.getKey(path: String, valueType: Class): T? { - try { + return try { val json: String = getSharedPrefs().getString(path, null) ?: return null - return parseJson(json, valueType.kotlin) - } catch (e: Exception) { - return null + parseJson(json, valueType.kotlin) + } catch (_: Exception) { + null } } @@ -193,21 +193,37 @@ object DataStore { setKey(getFolderName(folder, path), value) } + @Deprecated( + message = "Use parseJson(this) directly instead.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith( + expression = "parseJson(this)", + imports = ["com.lagradost.cloudstream3.utils.AppUtils.parseJson"], + ), + ) inline fun String.toKotlinObject(): T { return parseJson(this) } + @Deprecated( + message = "Use parseJson(this) directly instead.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith( + expression = "parseJson(this)", + imports = ["com.lagradost.cloudstream3.utils.AppUtils.parseJson"], + ), + ) fun String.toKotlinObject(valueType: Class): T { return parseJson(this, valueType.kotlin) } // GET KEY GIVEN PATH AND DEFAULT VALUE, NULL IF ERROR inline fun Context.getKey(path: String, defVal: T?): T? { - try { + return try { val json: String = getSharedPrefs().getString(path, null) ?: return defVal - return json.toKotlinObject() - } catch (e: Exception) { - return null + parseJson(json) + } catch (_: Exception) { + null } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt index 4c0717b3f..b5b367262 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt @@ -149,10 +149,12 @@ object TvChannelUtils { .setInputId(inputId) .build() - val channelUri = context.contentResolver.insert( - TvContractCompat.Channels.CONTENT_URI, - channel.toContentValues() - ) + val channelUri = runCatching { + context.contentResolver.insert( + TvContractCompat.Channels.CONTENT_URI, + channel.toContentValues() + ) + }.getOrNull() channelUri?.let { val channelId = ContentUris.parseId(it) diff --git a/app/src/main/res/drawable/ic_baseline_clock_24.xml b/app/src/main/res/drawable/ic_baseline_clock_24.xml new file mode 100644 index 000000000..3436c6208 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_clock_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-port/player_select_source_priority.xml b/app/src/main/res/layout-port/player_select_source_priority.xml index 2cba9c869..926227c58 100644 --- a/app/src/main/res/layout-port/player_select_source_priority.xml +++ b/app/src/main/res/layout-port/player_select_source_priority.xml @@ -1,6 +1,5 @@ - - + @@ -33,14 +32,21 @@ android:textSize="20sp" android:textStyle="bold" /> + + + android:src="@drawable/baseline_help_outline_24" /> - + + tools:ignore="LabelFor" + tools:text="@string/profile_number" /> + android:text="@string/sort_save" /> + android:text="@string/sort_close" /> diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 99a764dee..8d78c01b3 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -227,6 +227,12 @@ tools:listitem="@layout/homepage_parent" tools:visibility="gone" /> + + + + + + + @@ -1076,6 +1085,8 @@ diff --git a/app/src/main/res/layout/player_custom_layout_tv.xml b/app/src/main/res/layout/player_custom_layout_tv.xml index 077929d87..c6445d80f 100644 --- a/app/src/main/res/layout/player_custom_layout_tv.xml +++ b/app/src/main/res/layout/player_custom_layout_tv.xml @@ -352,6 +352,22 @@ android:layout_marginEnd="32dp" android:orientation="vertical"> + + diff --git a/app/src/main/res/layout/player_select_source_priority.xml b/app/src/main/res/layout/player_select_source_priority.xml index 182cd1861..0d825de77 100644 --- a/app/src/main/res/layout/player_select_source_priority.xml +++ b/app/src/main/res/layout/player_select_source_priority.xml @@ -160,6 +160,13 @@ + + - + android:orientation="horizontal"> + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/source_profile_settings_dialog.xml b/app/src/main/res/layout/source_profile_settings_dialog.xml new file mode 100644 index 000000000..3f1f39fed --- /dev/null +++ b/app/src/main/res/layout/source_profile_settings_dialog.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/trailer_custom_layout.xml b/app/src/main/res/layout/trailer_custom_layout.xml index 88a318874..b1dc96479 100644 --- a/app/src/main/res/layout/trailer_custom_layout.xml +++ b/app/src/main/res/layout/trailer_custom_layout.xml @@ -102,6 +102,8 @@ @@ -169,6 +171,12 @@ android:layout_marginEnd="32dp" android:orientation="vertical"> + + Працягнуць прагляд Выдаліць Больш інфармацыі - \@string/home_play + @string/home_play Для карэктнай працы гэтага пастаўшчыка можа спатрэбіцца VPN Гэты пастаўшчык — Torrent, рэкамендуецца VPN Вэб-сайт не пастаўляе метададзеных, загрузіць відэа не ўдасца, калі на сайце яго няма. diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index e90608793..b6edbd1cd 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -3,7 +3,7 @@ পোস্টার ক্লাউডস্ট্রিম দিয়ে চালান হোম - অভিনয়েঃ %s + অভিনয়ে: %s %1$dদিন %2$dঘন্টা %3$dমিনিট %1$dঘন্টা %2$dমিনিট %d মিনিট @@ -22,7 +22,7 @@ খুঁজুন ডাউনলোডসমূহ সেটিংস - %1$s এপি %2$d + %1$s পর্ব %2$d পর্ব %d মুক্তির তারিখ শেয়ার ব্রাউজারে খুলুন @@ -131,8 +131,8 @@ স্টোরেজ এর অনুমতি অনুপস্থিত। দয়া করে আবার চেষ্টা করুন। তথ্য ট্রেইলার প্রদর্শন করুন - কিটসু হতে পোস্টারসমূহ প্রদর্শন করুন - স্বয়ংক্রিয়ভাবে প্লাগিন এর হালনাগাদ + Kitsu থেকে পোস্টারসমূহ প্রদর্শন করুন + স্বয়ংক্রিয় প্লাগইন আপডেট স্বয়ংক্রিয়ভাবে প্লাগিনসমুহের ডাউনলোড হালনাগাদ ও ব্যাকআপ অ্যাপ এর হালনাগাদ দেখান @@ -148,9 +148,9 @@ সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন ফাইল ডিলিট মান ডিফল্ট এ রিসেট করুন - স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন + স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন যোগ। সিনেমা - এনিমে এর ফিলার পর্ব দেখায় + অ্যানিমে ফিলার পর্ব প্রদর্শন টিভি সিরিজ লিংক পাওয়া যায়নি চা খাওয়ানো হয়েছে @@ -174,7 +174,7 @@ ডিলিট শুরু কার্টুন - কিছু ফোন নতুন প্যাকেজ ইনস্টলার সাপোর্ট করে না। যদি আপডেটগুলি ইনস্টল না হয় তবে পুরোনো পদ্ধতি ব্যবহার করে দেখুন। + Some devices do not support the new package installer. Try the legacy option if updates do not install। সাবটাইটেল নেই এই প্রোভাইডার ক্রোমকাস্ট সাপোর্ট করে না উন্নত অনুসন্ধান @@ -204,7 +204,7 @@ আমাদের তৈরি Anime দেখার অ্যাপ্লিকেশন 18+ এনিমে - অনুসন্ধান ফলাফলে নির্বাচিত ভিডিও কুয়ালিটি লুকান + অনুসন্ধানের ফলে নির্বাচিত ভিডিওর মান লুকান অ্যাপ লাইভস্ট্রিম APK ইনস্টলার @@ -354,4 +354,37 @@ স্পিচ রিকগনিশন উপলব্ধ নেই কথা বলা শুরু করুন… ডাউনলোড তালিকা + সম্পূর্ণ সিরিজ প্লে করুন + এই ভিডিওটি একটি টরেন্ট, অর্থাৎ আপনার ভিডিও অ্যাক্টিভিটি ট্র্যাক করা সম্ভব।\nএগোনোর আগে টরেন্টিং সম্পর্কে ভালোভাবে বুঝে নিন। + ডিলিট করার জন্য আইটেম সিলেক্ট করুন + বর্তমানে কোনো ডাউনলোড নেই। + বর্তমানে কোনো ডাউনলোড কিউতে নেই। + অফলাইনে দেখার জন্য উপলব্ধ + সব সিলেক্ট করুন + সব ডিসিলেক্ট করুন + লোকাল ভিডিও খুলুন + অতিরিক্ত ব্রাইটনেস + ডিসপ্লে ব্রাইটনেস ১০০% ছাড়িয়ে গেলে ব্রাইটনেস ফিল্টার চালু করুন + সার্চ সাজেশন + টাইপ করার সময় সার্চ সাজেশন দেখান + সাজেশন মুছে ফেলুন + প্লেয়ার মেটাডেটা ওভারলে প্রদর্শন + কাস্ট প্যানেল প্রদর্শন + প্রাক-মুক্তি সংস্করণ ইনস্টলেশন + প্রাক-মুক্তি সংস্করণ ইতোমধ্যে ইনস্টল রয়েছে। + প্রাক-মুক্তি সংস্করণ ইনস্টল করতে সমস্যা হয়েছে। + (%1$d | %2$s) মুছুন + সাবধান + আপনি কি নিম্নলিখিত আইটেমগুলো স্থায়ীভাবে মুছতে চান?\n\n%s + ফাইল মুছুন + আপনি কি নিশ্চিত যে আপনি %1$s-এ নিচের এপিসোডগুলো স্থায়ীভাবে মুছে ফেলতে চান?\n\n%2$s + আপনি নিচের সিরিজগুলোর সব এপিসোডও স্থায়ীভাবে মুছে ফেলবেন:\n\n%s + আপনি কি নিশ্চিত যে আপনি নিচের সিরিজটির সব এপিসোড স্থায়ীভাবে মুছে ফেলতে চান?\n\n%s + সঙ্গীত + অডিও বই + মিডিয়া + অডিও + পডকাস্ট + ভিডিও + এনকোডিং ত্রুটি diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-b+ca/strings.xml similarity index 99% rename from app/src/main/res/values-ca/strings.xml rename to app/src/main/res/values-b+ca/strings.xml index 495eb5e3c..621243c78 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-b+ca/strings.xml @@ -123,7 +123,7 @@ Continua mirant Eliminar Més info - \@string/home_play + @string/home_play Potser necessiteu una VPN per que aquest proveïdor funcioni correctament Aquest proveïdor és un Torrent, es recomana fer servir una VPN El lloc no proveeix metadades, la càrrega del video fallarà si no existeix al lloc. diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index 8a2424326..feec84263 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -682,4 +682,17 @@ Zet torrent aan in Instellingen/Providers/Media voorkeur Herstart de app en accepteer de Stream Torrent pop-up om verder te gaan. Handmatige Update Knop + Open de app met Vingerafdruk, Face ID, PIN, Pattern en Wachtwoord. + Seekbar preview + Zet preview thumbnail aan op seekbar + Software decodering + Software decodering staat de mogelijkheid toe om de speler videobestanden af te laten spelen op jouw apparaat, maar het kan een haperige of onstabiele kijkervaring zorgen op hoge resolutie. + Volume heeft de 100% overschreden + Update Plugins + Geen plugins zijn geupdate. + Speler meldingen + De speler melding om de kijkervaring van de achtergrond de besturen + Online + Hoeveel verschillende items kunnen in parallel gedownload worden + Parallel downloads diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index 35367a1b2..b69146c92 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -742,4 +742,13 @@ Wideo Zapowiedź Na żywo + Ustawienia profilu + Ukryj źródła z negatywnym priorytetem + Ukryj źródła z błędami + + %d ukryty odnośnik + %d ukryte odnośniki + %d ukrytych odnośników + %d ukrytych odnośników + diff --git a/app/src/main/res/values-sq/strings.xml b/app/src/main/res/values-b+sq/strings.xml similarity index 99% rename from app/src/main/res/values-sq/strings.xml rename to app/src/main/res/values-b+sq/strings.xml index a13f87bfa..c33ea0447 100644 --- a/app/src/main/res/values-sq/strings.xml +++ b/app/src/main/res/values-b+sq/strings.xml @@ -127,7 +127,7 @@ Vazhdo shikimin Hiq Më shumë informacion - \@string/home_play + @string/home_play Një VPN mund të nevojitet që ky ofrues të funksionojë në rregull Ky ofrues është Torrent, rekomandohet një VPN Metadata nuk ofrohet nga kjo faqe, ngarkimi i videos do të dështojë nëse nuk ekziston në këtë faqe. @@ -735,4 +735,11 @@ %d shkarkime në radhë Live + Cilësimet e profil-it + Fshih burimet me prioritet negativ + Fshih burimet me error-e + + %d link i fshehur + %d linqe të fshehura + diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index 3b2b3876f..53679c05c 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -36,7 +36,7 @@ Tập tiếp theo Thể loại Chia sẻ - Mở bằng trình duyệt + Phát bằng Trình duyệt Bỏ tải Đang tải… Đang xem @@ -255,13 +255,13 @@ Chất lượng xem ưu tiên (WiFi) Số ký tự tối đa tiêu đề trình phát Hiển thị thông tin trình phát - Kích thước bộ nhớ đệm video - Thời lượng bộ nhớ đệm + Kích thước bộ đệm video + Thời lượng bộ đệm video Bộ nhớ đệm video trên thiết bị Xoá bộ nhớ đệm hình ảnh và video Sẽ gây lỗi nếu đặt quá cao trên thiết bị có bộ nhớ thấp như Android TV. - Sẽ gây lỗi nếu đặt quá cao trên máy có dung lượng lưu trữ thấp như Android TV. - DNS over HTTPS + Sẽ gây lỗi nếu đặt quá cao trên thiết bị có dung lượng lưu trữ thấp như Android TV. + DNS qua HTTPS Rất hữu ích để bỏ chặn ISP Bản sao trang web Xoá trang web diff --git a/app/src/main/res/values/donottranslate-strings.xml b/app/src/main/res/values/donottranslate-strings.xml index 6a4c82713..bb222918c 100644 --- a/app/src/main/res/values/donottranslate-strings.xml +++ b/app/src/main/res/values/donottranslate-strings.xml @@ -101,6 +101,7 @@ opensubtitles_key subdl_key animeskip_key + tv_layout_clock_key pref_category_security_key pref_category_gestures_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31cf951cf..eed41ce81 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -374,6 +374,8 @@ Looks Features General + Show real time clock + Show a real time clock at the top of the screen. Applies to the homepage and player Random Button Show random button on Homepage and Library Extension languages @@ -622,6 +624,14 @@ Subscribe Unsubscribe Profile %d + Profile settings + Hide sources with a negative priority + Hide sources with errors + + %d hidden link + %d hidden links + + Wi-Fi Mobile data Set default @@ -783,5 +793,4 @@ %d downloads queued Live - diff --git a/app/src/main/res/xml/settings_ui.xml b/app/src/main/res/xml/settings_ui.xml index 1b516ffa3..774724f99 100644 --- a/app/src/main/res/xml/settings_ui.xml +++ b/app/src/main/res/xml/settings_ui.xml @@ -89,6 +89,12 @@ android:icon="@drawable/metadata_overlay_icon" android:key="@string/show_player_metadata_key" android:title="@string/show_player_metadata_overlay" /> + (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; @@ -6632,6 +6633,21 @@ public final class com/lagradost/cloudstream3/metaproviders/TmdbLink { public fun toString ()Ljava/lang/String; } +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbLink$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbLink$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbLink; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbLink;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbLink$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/metaproviders/TmdbProvider : com/lagradost/cloudstream3/MainAPI { public fun ()V public fun fetchContentRating (Ljava/lang/Integer;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -6650,6 +6666,684 @@ public class com/lagradost/cloudstream3/metaproviders/TmdbProvider : com/lagrado public fun search (Ljava/lang/String;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember; + public fun equals (Ljava/lang/Object;)Z + public final fun getCharacter ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getProfilePath ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating; + public fun equals (Ljava/lang/Object;)Z + public final fun getCountry ()Ljava/lang/String; + public final fun getRating ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; + public fun equals (Ljava/lang/Object;)Z + public final fun getResults ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public fun equals (Ljava/lang/Object;)Z + public final fun getCast ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/Integer; + public final fun component5 ()Ljava/lang/Integer; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/String; + public final fun component8 ()Ljava/lang/Double; + public final fun component9 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode; + public fun equals (Ljava/lang/Object;)Z + public final fun getAirDate ()Ljava/lang/String; + public final fun getEpisodeNumber ()Ljava/lang/Integer; + public final fun getExternalIds ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun getId ()Ljava/lang/Integer; + public final fun getName ()Ljava/lang/String; + public final fun getOverview ()Ljava/lang/String; + public final fun getSeasonNumber ()Ljava/lang/Integer; + public final fun getStillPath ()Ljava/lang/String; + public final fun getVoteAverage ()Ljava/lang/Double; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre;Ljava/lang/Integer;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre; + public fun equals (Ljava/lang/Object;)Z + public final fun getId ()Ljava/lang/Integer; + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public fun equals (Ljava/lang/Object;)Z + public final fun getImdbId ()Ljava/lang/String; + public final fun getTvdbId ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component10 ()Ljava/lang/String; + public final fun component11 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun component12 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public final fun component13 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public final fun component14 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun component15 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun component16 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/Double; + public final fun component8 ()Ljava/util/List; + public final fun component9 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail; + public fun equals (Ljava/lang/Object;)Z + public final fun getCredits ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public final fun getDisplayTitle ()Ljava/lang/String; + public final fun getExternalIds ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun getGenres ()Ljava/util/List; + public final fun getId ()Ljava/lang/Integer; + public final fun getImdbId ()Ljava/lang/String; + public final fun getOriginalTitle ()Ljava/lang/String; + public final fun getOverview ()Ljava/lang/String; + public final fun getPosterPath ()Ljava/lang/String; + public final fun getRecommendations ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun getReleaseDate ()Ljava/lang/String; + public final fun getReleaseDates ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; + public final fun getRuntime ()Ljava/lang/Integer; + public final fun getSimilar ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun getTitle ()Ljava/lang/String; + public final fun getVideos ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public final fun getVoteAverage ()Ljava/lang/Double; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getResults ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$Companion; + public fun ()V + public fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;)V + public synthetic fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/Integer; + public final fun component3 ()Ljava/lang/Integer; + public final fun copy (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getResults ()Ljava/util/List; + public final fun getTotalPages ()Ljava/lang/Integer; + public final fun getTotalResults ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/String;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry; + public fun equals (Ljava/lang/Object;)Z + public final fun getCertification ()Ljava/lang/String; + public final fun getType ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getCountry ()Ljava/lang/String; + public final fun getReleaseDates ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; + public fun equals (Ljava/lang/Object;)Z + public final fun getResults ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component10 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/Double; + public final fun component8 ()Ljava/lang/String; + public final fun component9 ()Ljava/lang/String; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getDisplayTitle ()Ljava/lang/String; + public final fun getFirstAirDate ()Ljava/lang/String; + public final fun getId ()Ljava/lang/Integer; + public final fun getMediaType ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getOriginalName ()Ljava/lang/String; + public final fun getOriginalTitle ()Ljava/lang/String; + public final fun getPosterPath ()Ljava/lang/String; + public final fun getReleaseDate ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun getVoteAverage ()Ljava/lang/Double; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public final fun isTv ()Z + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/util/List;)V + public synthetic fun (Ljava/lang/Integer;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/util/List; + public final fun copy (Ljava/lang/Integer;Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail;Ljava/lang/Integer;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail; + public fun equals (Ljava/lang/Object;)Z + public final fun getEpisodes ()Ljava/util/List; + public final fun getSeasonNumber ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/Integer;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component2 ()Ljava/lang/Integer; + public final fun copy (Ljava/lang/Integer;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary; + public fun equals (Ljava/lang/Object;)Z + public final fun getEpisodeCount ()Ljava/lang/Integer; + public final fun getSeasonNumber ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$Companion; + public fun ()V + public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Integer; + public final fun component10 ()Ljava/util/List; + public final fun component11 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun component12 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public final fun component13 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public final fun component14 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun component15 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun component16 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Ljava/lang/Double; + public final fun component8 ()Ljava/util/List; + public final fun component9 ()Ljava/util/List; + public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail; + public fun equals (Ljava/lang/Object;)Z + public final fun getContentRatings ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; + public final fun getCredits ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; + public final fun getDisplayTitle ()Ljava/lang/String; + public final fun getEpisodeRunTime ()Ljava/util/List; + public final fun getExternalIds ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; + public final fun getFirstAirDate ()Ljava/lang/String; + public final fun getGenres ()Ljava/util/List; + public final fun getId ()Ljava/lang/Integer; + public final fun getName ()Ljava/lang/String; + public final fun getOriginalName ()Ljava/lang/String; + public final fun getOverview ()Ljava/lang/String; + public final fun getPosterPath ()Ljava/lang/String; + public final fun getRecommendations ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun getSeasons ()Ljava/util/List; + public final fun getSimilar ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; + public final fun getVideos ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public final fun getVoteAverage ()Ljava/lang/Double; + public final fun getYear ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo; + public fun equals (Ljava/lang/Object;)Z + public final fun getKey ()Ljava/lang/String; + public final fun getSite ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos { + public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$Companion; + public fun ()V + public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public fun equals (Ljava/lang/Object;)Z + public final fun getResults ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public class com/lagradost/cloudstream3/metaproviders/TraktProvider : com/lagradost/cloudstream3/MainAPI { public fun ()V public fun getHasMainPage ()Z @@ -7235,6 +7929,10 @@ public final class com/lagradost/cloudstream3/mvvm/ArchComponentExtKt { public static final fun throwAbleToResource (Ljava/lang/Throwable;)Lcom/lagradost/cloudstream3/mvvm/Resource; } +public final class com/lagradost/cloudstream3/mvvm/ArchComponentExt_jvmCommonKt { + public static final fun platformThrowAbleToResource (Ljava/lang/Throwable;)Lcom/lagradost/cloudstream3/mvvm/Resource; +} + public final class com/lagradost/cloudstream3/mvvm/DebugException : java/lang/Exception { public fun (Ljava/lang/String;)V } @@ -7438,6 +8136,10 @@ public final class com/lagradost/cloudstream3/utils/Coroutines { public final fun threadSafeListOf ([Ljava/lang/Object;)Ljava/util/List; } +public final class com/lagradost/cloudstream3/utils/Coroutines_jvmCommonKt { + public static final fun getWorkerDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; +} + public final class com/lagradost/cloudstream3/utils/Coroutines_jvmKt { public static final fun runOnMainThreadNative (Lkotlin/jvm/functions/Function0;)V } @@ -7625,44 +8327,6 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser { public final fun parse (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist; } -public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$C { - public static final field CENC_TYPE_cbc1 Ljava/lang/String; - public static final field CENC_TYPE_cbcs Ljava/lang/String; - public static final field CENC_TYPE_cenc Ljava/lang/String; - public static final field CENC_TYPE_cens Ljava/lang/String; - public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$C; - public static final field ROLE_FLAG_ALTERNATE I - public static final field ROLE_FLAG_CAPTION I - public static final field ROLE_FLAG_COMMENTARY I - public static final field ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND I - public static final field ROLE_FLAG_DESCRIBES_VIDEO I - public static final field ROLE_FLAG_DUB I - public static final field ROLE_FLAG_EASY_TO_READ I - public static final field ROLE_FLAG_EMERGENCY I - public static final field ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY I - public static final field ROLE_FLAG_MAIN I - public static final field ROLE_FLAG_SIGN I - public static final field ROLE_FLAG_SUBTITLE I - public static final field ROLE_FLAG_SUPPLEMENTARY I - public static final field ROLE_FLAG_TRANSCRIBES_DIALOG I - public static final field ROLE_FLAG_TRICK_PLAY I - public static final field SELECTION_FLAG_AUTOSELECT I - public static final field SELECTION_FLAG_DEFAULT I - public static final field SELECTION_FLAG_FORCED I - public static final field TRACK_TYPE_AUDIO I - public static final field TRACK_TYPE_CAMERA_MOTION I - public static final field TRACK_TYPE_DEFAULT I - public static final field TRACK_TYPE_IMAGE I - public static final field TRACK_TYPE_METADATA I - public static final field TRACK_TYPE_NONE I - public static final field TRACK_TYPE_TEXT I - public static final field TRACK_TYPE_UNKNOWN I - public static final field TRACK_TYPE_VIDEO I - public final fun getCLEARKEY_UUID ()Ljava/util/UUID; - public final fun getPLAYREADY_UUID ()Ljava/util/UUID; - public final fun getWIDEVINE_UUID ()Ljava/util/UUID; -} - public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$DrmInitData { public fun (Ljava/lang/String;[Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;)V public final fun component1 ()Ljava/lang/String; @@ -7928,7 +8592,7 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserExce public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$PsshAtomUtil { public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$PsshAtomUtil; - public final fun buildPsshAtom (Ljava/util/UUID;[Ljava/util/UUID;[B)[B + public final fun buildPsshAtom (Lkotlin/uuid/Uuid;[Lkotlin/uuid/Uuid;[B)[B } public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition { @@ -7949,19 +8613,19 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition } public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData { - public fun (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[B)V - public synthetic fun (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[BILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/UUID; + public fun (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[B)V + public synthetic fun (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[BILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lkotlin/uuid/Uuid; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()[B - public final fun copy (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[B)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[BILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public final fun copy (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[B)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[BILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; public fun equals (Ljava/lang/Object;)Z public final fun getData ()[B public final fun getLicenseServerUrl ()Ljava/lang/String; public final fun getMimeType ()Ljava/lang/String; - public final fun getUuid ()Ljava/util/UUID; + public final fun getUuid ()Lkotlin/uuid/Uuid; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 18579eb0c..59a36ce7f 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -59,12 +59,12 @@ kotlin { implementation(libs.kotlinx.atomicfu) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.io.core) implementation(libs.kotlinx.serialization.json) // JSON Parser implementation(libs.ksoup) // HTML Parser implementation(libs.ktor.http) implementation(libs.nicehttp) // HTTP Library implementation(libs.rhino) // Run JavaScript - implementation(libs.tmdb.java) // TMDB API v3 Wrapper Made with RetroFit implementation(libs.bundles.cryptography) // Cryptography } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt index 89f935da3..88dda631a 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt @@ -1,7 +1,9 @@ package com.lagradost.cloudstream3.metaproviders +import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.Actor +import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.Episode import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.HomePageList @@ -20,6 +22,8 @@ import com.lagradost.cloudstream3.SearchResponseList import com.lagradost.cloudstream3.TvSeriesLoadResponse import com.lagradost.cloudstream3.TvSeriesSearchResponse import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.addDate +import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.newEpisode import com.lagradost.cloudstream3.newHomePageResponse import com.lagradost.cloudstream3.newMovieLoadResponse @@ -28,35 +32,22 @@ import com.lagradost.cloudstream3.newTvSeriesLoadResponse import com.lagradost.cloudstream3.newTvSeriesSearchResponse import com.lagradost.cloudstream3.runAllAsync import com.lagradost.cloudstream3.toNewSearchResponseList +import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson -import com.uwetrottmann.tmdb2.Tmdb -import com.uwetrottmann.tmdb2.entities.AppendToResponse -import com.uwetrottmann.tmdb2.entities.BaseMovie -import com.uwetrottmann.tmdb2.entities.BaseTvShow -import com.uwetrottmann.tmdb2.entities.CastMember -import com.uwetrottmann.tmdb2.entities.ContentRating -import com.uwetrottmann.tmdb2.entities.Movie -import com.uwetrottmann.tmdb2.entities.ReleaseDate -import com.uwetrottmann.tmdb2.entities.ReleaseDatesResult -import com.uwetrottmann.tmdb2.entities.TvSeason -import com.uwetrottmann.tmdb2.entities.TvShow -import com.uwetrottmann.tmdb2.entities.Videos -import com.uwetrottmann.tmdb2.enumerations.AppendToResponseItem -import com.uwetrottmann.tmdb2.enumerations.VideoType -import retrofit2.awaitResponse -import retrofit2.Response -import java.util.Calendar +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable /** * episode and season starting from 1 * they are null if movie - * */ + */ +@Serializable data class TmdbLink( - @JsonProperty("imdbID") val imdbID: String?, - @JsonProperty("tmdbID") val tmdbID: Int?, - @JsonProperty("episode") val episode: Int?, - @JsonProperty("season") val season: Int?, - @JsonProperty("movieName") val movieName: String? = null, + @JsonProperty("imdbID") @SerialName("imdbID") val imdbID: String?, + @JsonProperty("tmdbID") @SerialName("tmdbID") val tmdbID: Int?, + @JsonProperty("episode") @SerialName("episode") val episode: Int?, + @JsonProperty("season") @SerialName("season") val season: Int?, + @JsonProperty("movieName") @SerialName("movieName") val movieName: String? = null, ) open class TmdbProvider : MainAPI() { @@ -67,19 +58,184 @@ open class TmdbProvider : MainAPI() { open val useMetaLoadResponse = false open val apiName = "TMDB" - // As some sites doesn't support s0 + // As some sites don't support s0 open val disableSeasonZero = true override val hasMainPage = true override val providerType = ProviderType.MetaProvider - // Fuck it, public private api key because github actions won't co-operate. - // Please no stealy. - private val tmdb = Tmdb("e6333b32409e02a4a6eba6fb7ff866bb") + private val tmdbApiKey = "e6333b32409e02a4a6eba6fb7ff866bb" + private val tmdbApiUrl = "https://api.themoviedb.org/3" + + @Serializable + data class TmdbIds( + @JsonProperty("imdb_id") @SerialName("imdb_id") val imdbId: String? = null, + @JsonProperty("tvdb_id") @SerialName("tvdb_id") val tvdbId: Int? = null, + ) + + @Serializable + data class TmdbGenre( + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, + ) + + @Serializable + data class TmdbCastMember( + @JsonProperty("name") @SerialName("name") val name: String? = null, + @JsonProperty("character") @SerialName("character") val character: String? = null, + @JsonProperty("profile_path") @SerialName("profile_path") val profilePath: String? = null, + ) + + @Serializable + data class TmdbCredits( + @JsonProperty("cast") @SerialName("cast") val cast: List? = null, + ) + + @Serializable + data class TmdbVideo( + @JsonProperty("key") @SerialName("key") val key: String? = null, + @JsonProperty("site") @SerialName("site") val site: String? = null, + @JsonProperty("type") @SerialName("type") val type: String? = null, + ) + + @Serializable + data class TmdbVideos( + @JsonProperty("results") @SerialName("results") val results: List? = null, + ) + + // Shared between movie and tv search results + @Serializable + data class TmdbSearchResult( + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, // movies + @JsonProperty("original_title") @SerialName("original_title") val originalTitle: String? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, // tv + @JsonProperty("original_name") @SerialName("original_name") val originalName: String? = null, + @JsonProperty("poster_path") @SerialName("poster_path") val posterPath: String? = null, + @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, + @JsonProperty("release_date") @SerialName("release_date") val releaseDate: String? = null, + @JsonProperty("first_air_date") @SerialName("first_air_date") val firstAirDate: String? = null, + @JsonProperty("media_type") @SerialName("media_type") val mediaType: String? = null, // for multi-search + ) { + @get:JsonIgnore val isTv get() = name != null || mediaType == "tv" + @get:JsonIgnore val displayTitle get() = title ?: originalTitle ?: name ?: originalName ?: "" + @get:JsonIgnore val year get() = (releaseDate ?: firstAirDate)?.take(4)?.toIntOrNull() + } + + @Serializable + data class TmdbPageResult( + @JsonProperty("results") @SerialName("results") val results: List? = null, + @JsonProperty("total_pages") @SerialName("total_pages") val totalPages: Int? = null, + @JsonProperty("total_results") @SerialName("total_results") val totalResults: Int? = null, + ) + + @Serializable + data class TmdbMultiResult( + @JsonProperty("results") @SerialName("results") val results: List? = null, + ) + + @Serializable + data class TmdbEpisode( + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("episode_number") @SerialName("episode_number") val episodeNumber: Int? = null, + @JsonProperty("season_number") @SerialName("season_number") val seasonNumber: Int? = null, + @JsonProperty("still_path") @SerialName("still_path") val stillPath: String? = null, + @JsonProperty("air_date") @SerialName("air_date") val airDate: String? = null, + @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, + @JsonProperty("external_ids") @SerialName("external_ids") val externalIds: TmdbIds? = null, + ) + + @Serializable + data class TmdbSeasonDetail( + @JsonProperty("season_number") @SerialName("season_number") val seasonNumber: Int? = null, + @JsonProperty("episodes") @SerialName("episodes") val episodes: List? = null, + ) + + @Serializable + data class TmdbSeasonSummary( + @JsonProperty("season_number") @SerialName("season_number") val seasonNumber: Int? = null, + @JsonProperty("episode_count") @SerialName("episode_count") val episodeCount: Int? = null, + ) + + @Serializable + data class TmdbContentRating( + @JsonProperty("iso_3166_1") @SerialName("iso_3166_1") val country: String? = null, + @JsonProperty("rating") @SerialName("rating") val rating: String? = null, + ) + + @Serializable + data class TmdbContentRatings( + @JsonProperty("results") @SerialName("results") val results: List? = null, + ) + + @Serializable + data class TmdbReleaseDateEntry( + @JsonProperty("certification") @SerialName("certification") val certification: String? = null, + @JsonProperty("type") @SerialName("type") val type: Int? = null, + ) + + @Serializable + data class TmdbReleaseDateResult( + @JsonProperty("iso_3166_1") @SerialName("iso_3166_1") val country: String? = null, + @JsonProperty("release_dates") @SerialName("release_dates") val releaseDates: List? = null, + ) + + @Serializable + data class TmdbReleaseDates( + @JsonProperty("results") @SerialName("results") val results: List? = null, + ) + + @Serializable + data class TmdbTvDetail( + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("name") @SerialName("name") val name: String? = null, + @JsonProperty("original_name") @SerialName("original_name") val originalName: String? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("poster_path") @SerialName("poster_path") val posterPath: String? = null, + @JsonProperty("first_air_date") @SerialName("first_air_date") val firstAirDate: String? = null, + @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, + @JsonProperty("genres") @SerialName("genres") val genres: List? = null, + @JsonProperty("episode_run_time") @SerialName("episode_run_time") val episodeRunTime: List? = null, + @JsonProperty("seasons") @SerialName("seasons") val seasons: List? = null, + @JsonProperty("external_ids") @SerialName("external_ids") val externalIds: TmdbIds? = null, + @JsonProperty("videos") @SerialName("videos") val videos: TmdbVideos? = null, + @JsonProperty("credits") @SerialName("credits") val credits: TmdbCredits? = null, + @JsonProperty("recommendations") @SerialName("recommendations") val recommendations: TmdbPageResult? = null, + @JsonProperty("similar") @SerialName("similar") val similar: TmdbPageResult? = null, + @JsonProperty("content_ratings") @SerialName("content_ratings") val contentRatings: TmdbContentRatings? = null, + ) { + @get:JsonIgnore val displayTitle get() = name ?: originalName ?: "" + @get:JsonIgnore val year get() = firstAirDate?.take(4)?.toIntOrNull() + } + + @Serializable + data class TmdbMovieDetail( + @JsonProperty("id") @SerialName("id") val id: Int? = null, + @JsonProperty("title") @SerialName("title") val title: String? = null, + @JsonProperty("original_title") @SerialName("original_title") val originalTitle: String? = null, + @JsonProperty("overview") @SerialName("overview") val overview: String? = null, + @JsonProperty("poster_path") @SerialName("poster_path") val posterPath: String? = null, + @JsonProperty("release_date") @SerialName("release_date") val releaseDate: String? = null, + @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, + @JsonProperty("genres") @SerialName("genres") val genres: List? = null, + @JsonProperty("runtime") @SerialName("runtime") val runtime: Int? = null, + @JsonProperty("imdb_id") @SerialName("imdb_id") val imdbId: String? = null, + @JsonProperty("external_ids") @SerialName("external_ids") val externalIds: TmdbIds? = null, + @JsonProperty("videos") @SerialName("videos") val videos: TmdbVideos? = null, + @JsonProperty("credits") @SerialName("credits") val credits: TmdbCredits? = null, + @JsonProperty("recommendations") @SerialName("recommendations") val recommendations: TmdbPageResult? = null, + @JsonProperty("similar") @SerialName("similar") val similar: TmdbPageResult? = null, + @JsonProperty("release_dates") @SerialName("release_dates") val releaseDates: TmdbReleaseDates? = null, + ) { + @get:JsonIgnore val displayTitle get() = title ?: originalTitle ?: "" + @get:JsonIgnore val year get() = releaseDate?.take(4)?.toIntOrNull() + } private fun getImageUrl(link: String?): String? { - if (link == null) return null - return if (link.startsWith("/")) "https://image.tmdb.org/t/p/w500/$link" else link + link ?: return null + return if (link.startsWith("/")) "https://image.tmdb.org/t/p/w500$link" else link } private fun getUrl(id: Int?, tvShow: Boolean): String { @@ -87,199 +243,197 @@ open class TmdbProvider : MainAPI() { else "https://www.themoviedb.org/movie/${id ?: -1}" } - private fun BaseTvShow.toSearchResponse(): TvSeriesSearchResponse { - return newTvSeriesSearchResponse( - name = this.name ?: this.original_name, + private suspend fun getApi(path: String, extraParams: Map = emptyMap()): String { + val params = buildMap { + put("api_key", tmdbApiKey) + putAll(extraParams) + } + return app.get( + url = "$tmdbApiUrl$path", + params = params, + ).text + } + + private fun TmdbSearchResult.toSearchResponse() = if (isTv) { + newTvSeriesSearchResponse( + name = displayTitle, url = getUrl(id, true), type = TvType.TvSeries, - fix = false + fix = false, ) { this.id = this@toSearchResponse.id - this.posterUrl = getImageUrl(poster_path) - this.score = Score.from10(vote_average) - this.year = first_air_date?.let { - Calendar.getInstance().apply { - time = it - }.get(Calendar.YEAR) - } + this.posterUrl = getImageUrl(posterPath) + this.score = Score.from10(voteAverage) + this.year = this@toSearchResponse.year } - } - - private fun BaseMovie.toSearchResponse(): MovieSearchResponse { - return newMovieSearchResponse( - name = this.title ?: this.original_title, + } else { + newMovieSearchResponse( + name = displayTitle, url = getUrl(id, false), type = TvType.Movie, - fix = false + fix = false, ) { this.id = this@toSearchResponse.id - this.posterUrl = getImageUrl(poster_path) - this.score = Score.from10(vote_average) - this.year = release_date?.let { - Calendar.getInstance().apply { - time = it - }.get(Calendar.YEAR) - } + this.posterUrl = getImageUrl(posterPath) + this.score = Score.from10(voteAverage) + this.year = this@toSearchResponse.year } } - private fun List?.toActors(): List>? { + private fun List?.toActors(): List>? { return this?.mapNotNull { + it ?: return@mapNotNull null Pair( - Actor(it?.name ?: return@mapNotNull null, getImageUrl(it.profile_path)), - it.character + Actor(it.name ?: return@mapNotNull null, getImageUrl(it.profilePath)), + it.character, ) } } - private suspend fun TvShow.toLoadResponse(): TvSeriesLoadResponse { - val tvSeasonsService = tmdb.tvSeasonsService() - val episodes = mutableListOf() - - val validSeasons = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 } ?: emptyList() - for (season in validSeasons) { - val seasonNumber = season.season_number ?: continue - - val response: Response = tmdb.tvSeasonsService() - .season(this.id, seasonNumber, "external_ids,images,episodes") - .awaitResponse() - - val fullSeason = response.body() ?: continue - - fullSeason.episodes?.forEach { episode -> - episodes += newEpisode( - TmdbLink( - episode.external_ids?.imdb_id ?: this.external_ids?.imdb_id, - this.id, - episode.episode_number, - episode.season_number, - this.name ?: this.original_name - ).toJson() - ) { - this.name = episode.name - this.season = episode.season_number - this.episode = episode.episode_number - this.score = Score.from10(episode.vote_average) - this.description = episode.overview - this.date = episode.air_date?.time - this.posterUrl = getImageUrl(episode.still_path) - } - } - } - - return newTvSeriesLoadResponse( - this.name ?: this.original_name, - getUrl(id, true), - TvType.TvSeries, - episodes - ) { - posterUrl = getImageUrl(poster_path) - year = first_air_date?.let { - Calendar.getInstance().apply { - time = it - }.get(Calendar.YEAR) - } - plot = overview - addImdbId(external_ids?.imdb_id) - tags = genres?.mapNotNull { it.name } - duration = episode_run_time?.average()?.toInt() - score = Score.from10(vote_average) - addTrailer(videos.toTrailers()) - recommendations = (this@toLoadResponse.recommendations - ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } - addActors(credits?.cast?.toList().toActors()) - contentRating = fetchContentRating(id, "US") - } - } - - private fun Videos?.toTrailers(): List? { - return this?.results?.filter { it.type != VideoType.OPENING_CREDITS && it.type != VideoType.FEATURETTE } - ?.sortedBy { it.type?.ordinal ?: 10000 } + private fun TmdbVideos?.toTrailers(): List? { + val skipTypes = setOf("Opening Credits", "Featurette") + return this?.results + ?.filter { it.type !in skipTypes } + ?.sortedBy { it.type } ?.mapNotNull { when (it.site?.trim()?.lowercase()) { - "youtube" -> { // TODO FILL SITES - "https://www.youtube.com/watch?v=${it.key}" - } + "youtube" -> "https://www.youtube.com/watch?v=${it.key}" else -> null } } } - private suspend fun Movie.toLoadResponse(): MovieLoadResponse { - return newMovieLoadResponse( - this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink( - this.imdb_id, - this.id, - null, - null, - this.title ?: this.original_title, - ).toJson() - ) { - posterUrl = getImageUrl(poster_path) - year = release_date?.let { - Calendar.getInstance().apply { - time = it - }.get(Calendar.YEAR) - } - plot = overview - addImdbId(external_ids?.imdb_id) - tags = genres?.mapNotNull { it.name } - duration = runtime - score = Score.from10(vote_average) - addTrailer(videos.toTrailers()) + open suspend fun fetchContentRating(id: Int?, country: String): String? { + id ?: return null + // Try TV content ratings first + val tvRating = parseJson( + getApi("/tv/$id/content_ratings") + ).results?.firstOrNull { it.country == country }?.rating + if (tvRating != null) return tvRating + // Fall back to movie release dates + return parseJson( + getApi("/movie/$id/release_dates") + ).results?.firstOrNull { it.country == country } + ?.releaseDates?.firstOrNull { !it.certification.isNullOrBlank() } + ?.certification + } + + private suspend fun TmdbTvDetail.toLoadResponse(): TvSeriesLoadResponse { + val episodes = mutableListOf() + val validSeasons = seasons?.filter { !disableSeasonZero || (it.seasonNumber ?: 0) != 0 } + ?: emptyList() + + for (season in validSeasons) { + val seasonNum = season.seasonNumber ?: continue + val fullSeason = parseJson( + getApi("/tv/$id/season/$seasonNum", mapOf("append_to_response" to "external_ids")) + ) + + fullSeason.episodes?.forEach { episode -> + episodes += newEpisode( + TmdbLink( + episode.externalIds?.imdbId ?: externalIds?.imdbId, + id, + episode.episodeNumber, + episode.seasonNumber, + displayTitle, + ).toJson() + ) { + this.name = episode.name + this.season = episode.seasonNumber + this.episode = episode.episodeNumber + this.score = Score.from10(episode.voteAverage) + this.description = episode.overview + this.posterUrl = getImageUrl(episode.stillPath) + this.addDate(episode.airDate) + } + } + } + + return newTvSeriesLoadResponse( + displayTitle, + getUrl(id, true), + TvType.TvSeries, + episodes, + ) { + posterUrl = getImageUrl(posterPath) + this.year = this@toLoadResponse.year + plot = overview + addImdbId(externalIds?.imdbId) + tags = genres?.mapNotNull { it.name } + duration = episodeRunTime?.average()?.toInt() + score = Score.from10(voteAverage) + addTrailer(videos.toTrailers()) recommendations = (this@toLoadResponse.recommendations ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } addActors(credits?.cast?.toList().toActors()) + contentRating = contentRatings?.results?.firstOrNull { it.country == "US" }?.rating + ?: fetchContentRating(id, "US") + } + } - contentRating = fetchContentRating(id, "US") + private suspend fun TmdbMovieDetail.toLoadResponse(): MovieLoadResponse { + return newMovieLoadResponse( + displayTitle, + getUrl(id, false), + TvType.Movie, + TmdbLink( + imdbId ?: externalIds?.imdbId, + id, + null, + null, + displayTitle, + ).toJson() + ) { + posterUrl = getImageUrl(posterPath) + this.year = this@toLoadResponse.year + plot = overview + addImdbId(imdbId ?: externalIds?.imdbId) + tags = genres?.mapNotNull { it.name } + duration = runtime + score = Score.from10(voteAverage) + addTrailer(videos.toTrailers()) + recommendations = (this@toLoadResponse.recommendations + ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } + addActors(credits?.cast?.toList().toActors()) + contentRating = releaseDates?.results + ?.firstOrNull { it.country == "US" } + ?.releaseDates?.firstOrNull { !it.certification.isNullOrBlank() } + ?.certification } } override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { - - // SAME AS DISCOVER IT SEEMS -// val popularSeries = tmdb.tvService().popular(page, "en-US").execute().body()?.results?.map { -// it.toSearchResponse() -// } ?: listOf() -// -// val popularMovies = -// tmdb.moviesService().popular(page, "en-US", "840").execute().body()?.results?.map { -// it.toSearchResponse() -// } ?: listOf() - var discoverMovies: List = listOf() var discoverSeries: List = listOf() var topMovies: List = listOf() var topSeries: List = listOf() runAllAsync( { - discoverMovies = tmdb.discoverMovie().page(page).build().awaitResponse().body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - }, { - discoverSeries = tmdb.discoverTv().page(page).build().awaitResponse().body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - }, { - // https://en.wikipedia.org/wiki/ISO_3166-1 - topMovies = - tmdb.moviesService().topRated(page, "en-US", "US").awaitResponse() - .body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - }, { - topSeries = - tmdb.tvService().topRated(page, "en-US").awaitResponse().body()?.results?.map { - it.toSearchResponse() - } ?: listOf() - } + discoverMovies = parseJson( + getApi("/discover/movie", mapOf("page" to "$page")) + ).results?.map { it.toSearchResponse() as MovieSearchResponse } ?: listOf() + }, + { + discoverSeries = parseJson( + getApi("/discover/tv", mapOf("page" to "$page")) + ).results?.map { it.toSearchResponse() as TvSeriesSearchResponse } ?: listOf() + }, + { + topMovies = parseJson( + getApi("/movie/top_rated", mapOf("page" to "$page", "language" to "en-US", "region" to "US")) + ).results?.map { it.toSearchResponse() as MovieSearchResponse } ?: listOf() + }, + { + topSeries = parseJson( + getApi("/tv/top_rated", mapOf("page" to "$page", "language" to "en-US")) + ).results?.map { it.toSearchResponse() as TvSeriesSearchResponse } ?: listOf() + }, ) return newHomePageResponse( listOf( -// HomePageList("Popular Series", popularSeries), -// HomePageList("Popular Movies", popularMovies), HomePageList("Popular Movies", discoverMovies), HomePageList("Popular Series", discoverSeries), HomePageList("Top Movies", topMovies), @@ -288,47 +442,14 @@ open class TmdbProvider : MainAPI() { ) } - open fun loadFromImdb(imdb: String, seasons: List): LoadResponse? { - return null - } + open fun loadFromImdb(imdb: String, seasons: List): LoadResponse? = null + open fun loadFromTmdb(tmdbId: Int, seasons: List): LoadResponse? = null + open fun loadFromImdb(imdb: String): LoadResponse? = null + open fun loadFromTmdb(tmdbId: Int): LoadResponse? = null - open fun loadFromTmdb(tmdb: Int, seasons: List): LoadResponse? { - return null - } - - open fun loadFromImdb(imdb: String): LoadResponse? { - return null - } - - open fun loadFromTmdb(tmdb: Int): LoadResponse? { - return null - } - - open suspend fun fetchContentRating(id: Int?, country: String): String? { - id ?: return null - - val contentRatings = tmdb.tvService().content_ratings(id).awaitResponse().body()?.results - return if (!contentRatings.isNullOrEmpty()) { - contentRatings.firstOrNull { it: ContentRating -> - it.iso_3166_1 == country - }?.rating - } else { - val releaseDates = tmdb.moviesService().releaseDates(id).awaitResponse().body()?.results - val certification = releaseDates?.firstOrNull { it: ReleaseDatesResult -> - it.iso_3166_1 == country - }?.release_dates?.firstOrNull { it: ReleaseDate -> - !it.certification.isNullOrBlank() - }?.certification - - certification - } - } - - // Possible to add recommendations and such here. override suspend fun load(url: String): LoadResponse? { // https://www.themoviedb.org/movie/7445-brothers // https://www.themoviedb.org/tv/71914-the-wheel-of-time - val idRegex = Regex("""themoviedb\.org/(.*)/(\d+)""") val found = idRegex.find(url) @@ -337,86 +458,64 @@ open class TmdbProvider : MainAPI() { ?: throw ErrorLoadingException("No id found") return if (useMetaLoadResponse) { - return if (isTvSeries) { - val body = tmdb.tvService() - .tv( - id, - "en-US", - AppendToResponse( - AppendToResponseItem.EXTERNAL_IDS, - AppendToResponseItem.VIDEOS + if (isTvSeries) { + val detail = parseJson( + getApi( + "/tv/$id", + mapOf( + "language" to "en-US", + "append_to_response" to "external_ids,videos,credits,recommendations,similar,content_ratings", ) ) - .awaitResponse().body() - val response = body?.toLoadResponse() - if (response != null) { - if (response.recommendations.isNullOrEmpty()) - tmdb.tvService().recommendations(id, 1, "en-US").awaitResponse().body() - ?.let { - it.results?.map { res -> res.toSearchResponse() } - }?.let { list -> - response.recommendations = list - } - - if (response.actors.isNullOrEmpty()) - tmdb.tvService().credits(id, "en-US").awaitResponse().body()?.let { - response.addActors(it.cast?.toActors()) - } - } - - response + ) + detail.toLoadResponse() } else { - val body = tmdb.moviesService() - .summary( - id, - "en-US", - AppendToResponse( - AppendToResponseItem.EXTERNAL_IDS, - AppendToResponseItem.VIDEOS + val detail = parseJson( + getApi( + "/movie/$id", + mapOf( + "language" to "en-US", + "append_to_response" to "external_ids,videos,credits,recommendations,similar,release_dates", ) ) - .awaitResponse().body() - val response = body?.toLoadResponse() - if (response != null) { - if (response.recommendations.isNullOrEmpty()) - tmdb.moviesService().recommendations(id, 1, "en-US").awaitResponse().body() - ?.let { - it.results?.map { res -> res.toSearchResponse() } - }?.let { list -> - response.recommendations = list - } - - if (response.actors.isNullOrEmpty()) - tmdb.moviesService().credits(id).awaitResponse().body()?.let { - response.addActors(it.cast?.toActors()) - } - } - response + ) + detail.toLoadResponse() } } else { loadFromTmdb(id)?.let { return it } if (isTvSeries) { - tmdb.tvService().externalIds(id).awaitResponse().body()?.imdb_id?.let { - val fromImdb = loadFromImdb(it) - val result = if (fromImdb == null) { - val details = tmdb.tvService().tv(id, "en-US").awaitResponse().body() - loadFromImdb(it, details?.seasons ?: listOf()) - ?: loadFromTmdb(id, details?.seasons ?: listOf()) - } else fromImdb - - result + val externalIds = parseJson(getApi("/tv/$id/external_ids")) + val imdbId = externalIds.imdbId + if (imdbId != null) { + val fromImdb = loadFromImdb(imdbId) + if (fromImdb != null) return fromImdb + } + val seasons = parseJson(getApi("/tv/$id")).seasons ?: listOf() + if (imdbId != null) { + loadFromImdb(imdbId, seasons) ?: loadFromTmdb(id, seasons) + } else { + loadFromTmdb(id, seasons) } } else { - tmdb.moviesService().externalIds(id).awaitResponse() - .body()?.imdb_id?.let { loadFromImdb(it) } + val imdbId = parseJson(getApi("/movie/$id")).imdbId + if (imdbId != null) loadFromImdb(imdbId) else null } } } override suspend fun search(query: String, page: Int): SearchResponseList? { - return tmdb.searchService().multi(query, page, "en-US", "US", includeAdult).awaitResponse() - .body()?.results?.mapNotNull { - it.movie?.toSearchResponse() ?: it.tvShow?.toSearchResponse() - }?.toNewSearchResponseList() + return parseJson( + getApi( + "/search/multi", + mapOf( + "query" to query, + "page" to "$page", + "language" to "en-US", + "include_adult" to "$includeAdult", + ) + ) + ).results?.mapNotNull { + if (it.mediaType == "person") null else it.toSearchResponse() + }?.toNewSearchResponseList() } -} \ No newline at end of file +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index 4cd8eefa6..73de880db 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -4,15 +4,11 @@ import androidx.annotation.AnyThread import com.lagradost.api.Log import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.utils.AppDebug +import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.WorkerThread import kotlinx.coroutines.* -import java.io.InterruptedIOException -import java.net.SocketTimeoutException -import java.net.UnknownHostException -import javax.net.ssl.SSLHandshakeException import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -import kotlin.reflect.full.NoSuchPropertyException const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!" const val DEBUG_PRINT = "DEBUG PRINT" @@ -61,7 +57,7 @@ sealed class Resource { companion object { fun fromResult(result: Result) : Resource { val value = result.getOrNull() - return if(value != null) { + return if (value != null) { Success(value) } else { throwAbleToResource(result.exceptionOrNull() ?: Exception("this should not be possible")) @@ -72,7 +68,6 @@ sealed class Resource { fun logError(throwable: Throwable) { Log.d("ApiError", "-------------------------------------------------------------------") - Log.d("ApiError", "safeApiCall: " + throwable.localizedMessage) Log.d("ApiError", "safeApiCall: " + throwable.message) throwable.printStackTrace() Log.d("ApiError", "-------------------------------------------------------------------") @@ -129,16 +124,21 @@ suspend fun suspendSafeApiCall(apiCall: suspend () -> T): T? { } fun Throwable.getAllMessages(): String { - return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") + return (this.message ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") } fun Throwable.getStackTracePretty(showMessage: Boolean = true): String { - val prefix = if (showMessage) this.localizedMessage?.let { "\n$it" } ?: "" else "" - return prefix + this.stackTrace.joinToString( - separator = "\n" - ) { - "${it.fileName} ${it.lineNumber}" - } + val prefix = if (showMessage) this.message?.let { "\n$it" } ?: "" else "" + return prefix + this.stackTraceToString() + .lines() + .mapNotNull { line -> + val trimmed = line.trim() + if (!trimmed.startsWith("at ")) return@mapNotNull null + // pull "File.kt:42" out of "at com.pkg.Class.method(File.kt:42)" + Regex("""\(([^)]+)\)$""").find(trimmed)?.groupValues?.get(1) + ?.replace(":", " ") + } + .joinToString("\n") } fun safeFail(throwable: Throwable): Resource { @@ -162,50 +162,26 @@ fun CoroutineScope.launchSafe( return this.launch(context, start, obj) } +expect fun platformThrowAbleToResource(throwable: Throwable): Resource + fun throwAbleToResource( throwable: Throwable ): Resource { return when (throwable) { - is NoSuchMethodException, is NoSuchFieldException, is NoSuchMethodError, is NoSuchFieldError, is NoSuchPropertyException -> { - Resource.Failure( - false, - "App or extension is outdated, update the app or try pre-release.\n${throwable.message}" // todo add exact version? - ) - } - is NullPointerException -> { - for (line in throwable.stackTrace) { - if (line?.fileName?.endsWith("provider.kt", ignoreCase = true) == true) { - return Resource.Failure( - false, - "NullPointerException at ${line.fileName} ${line.lineNumber}\nSite might have updated or added Cloudflare/DDOS protection" - ) - } + val traceLine = throwable.stackTraceToString() + .lines() + .firstOrNull { it.contains("provider.kt", ignoreCase = true) } + ?.let { Regex("""\(([^)]+)\)$""").find(it)?.groupValues?.get(1) } + if (traceLine != null) { + return Resource.Failure( + false, + "NullPointerException at $traceLine\nSite might have updated or added Cloudflare/DDOS protection" + ) } safeFail(throwable) } - is SocketTimeoutException, is InterruptedIOException -> { - Resource.Failure( - true, - "Connection Timeout\nPlease try again later." - ) - } -// is HttpException -> { -// Resource.Failure( -// false, -// throwable.statusCode, -// null, -// throwable.message ?: "HttpException" -// ) -// } - is UnknownHostException -> { - Resource.Failure( - true, - "Cannot connect to server, try again later.\n${throwable.message}" - ) - } - is ErrorLoadingException -> { Resource.Failure( true, @@ -217,20 +193,13 @@ fun throwAbleToResource( Resource.Failure(false, "This operation is not implemented.") } - is SSLHandshakeException -> { - Resource.Failure( - true, - (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS." - ) - } - is CancellationException -> { throwable.cause?.let { throwAbleToResource(it) } ?: safeFail(throwable) } - else -> safeFail(throwable) + else -> platformThrowAbleToResource(throwable) } } @@ -238,9 +207,9 @@ fun throwAbleToResource( suspend fun safeApiCall( @WorkerThread apiCall: suspend () -> T, ): Resource { - return withContext(Dispatchers.IO) { + return apiCall.ioWork { work -> try { - Resource.Success(apiCall.invoke()) + Resource.Success(work.invoke()) } catch (throwable: Throwable) { logError(throwable) throwAbleToResource(throwable) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt index 4536eb88b..4a269c525 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.* @AnyThread expect fun runOnMainThreadNative(@MainThread work: (() -> Unit)) +expect val workerDispatcher: CoroutineDispatcher internal expect annotation class WorkerThread() object Coroutines { @@ -24,7 +25,7 @@ object Coroutines { @AnyThread fun T.ioSafe(@WorkerThread work: suspend (CoroutineScope.(T) -> Unit)): Job { val value = this - return CoroutineScope(Dispatchers.IO).launchSafe { + return CoroutineScope(workerDispatcher).launchSafe { work(value) } } @@ -32,7 +33,7 @@ object Coroutines { @AnyThread suspend fun V.ioWorkSafe(@WorkerThread work: suspend (CoroutineScope.(V) -> T)): T? { val value = this - return withContext(Dispatchers.IO) { + return withContext(workerDispatcher) { try { work(value) } catch (e: Exception) { @@ -45,7 +46,7 @@ object Coroutines { @AnyThread suspend fun V.ioWork(@WorkerThread work: suspend (CoroutineScope.(V) -> T)): T { val value = this - return withContext(Dispatchers.IO) { + return withContext(workerDispatcher) { work(value) } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt index cd5d752b3..3edd0f410 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -21,9 +21,10 @@ package com.lagradost.cloudstream3.utils import com.lagradost.cloudstream3.base64DecodeArray import io.ktor.http.Url -import java.io.IOException -import java.nio.ByteBuffer -import java.util.UUID +import kotlinx.io.Buffer +import kotlinx.io.IOException +import kotlinx.io.readByteArray +import kotlin.uuid.Uuid @Suppress("unused") object HlsPlaylistParser { @@ -186,13 +187,13 @@ object HlsPlaylistParser { data class SchemeData( /** - * The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is universal (i.e. + * The [Uuid] of the DRM scheme, or [C.UUID_NIL] if the data is universal (i.e. * applies to all schemes). */ - val uuid: UUID, + val uuid: Uuid, /** The URL of the server to which license requests should be made. May be null if unknown. */ val licenseServerUrl: String? = null, - /** The mimeType of {@link #data}. */ + /** The mimeType of [data]. */ val mimeType: String, /** The initialization data. May be null for scheme support checks only. */ val data: ByteArray @@ -454,14 +455,14 @@ object HlsPlaylistParser { // "." or "..", remove the appropriate segments of the path. if (i == segmentStart + 1 && url[segmentStart] == '.') { // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". - url.delete(segmentStart, nextSegmentStart) + url.deleteRange(segmentStart, nextSegmentStart) limit -= nextSegmentStart - segmentStart i = segmentStart } else if (i == segmentStart + 2 && url[segmentStart] == '.' && url[segmentStart + 1] == '.') { // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". val prevSegmentStart = url.lastIndexOf("/", segmentStart - 2) + 1 val removeFrom = if (prevSegmentStart > offset) prevSegmentStart else offset - url.delete(removeFrom, nextSegmentStart) + url.deleteRange(removeFrom, nextSegmentStart) limit -= nextSegmentStart - removeFrom segmentStart = prevSegmentStart i = prevSegmentStart @@ -532,31 +533,31 @@ object HlsPlaylistParser { } } - object C { + private object C { /** - * UUID for the ClearKey DRM scheme. + * [Uuid] for the ClearKey DRM scheme. * * * ClearKey is supported on Android devices running Android 5.0 (API Level 21) and up. */ - val CLEARKEY_UUID = UUID(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) + val CLEARKEY_UUID = Uuid.fromLongs(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) /** - * UUID for the Widevine DRM scheme. + * [Uuid] for the Widevine DRM scheme. * * * Widevine is supported on Android devices running Android 4.3 (API Level 18) and up. */ - val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L) + val WIDEVINE_UUID = Uuid.fromLongs(-0x121074568629b532L, -0x5c37d8232ae2de13L) /** - * UUID for the PlayReady DRM scheme. + * [Uuid] for the PlayReady DRM scheme. * * * PlayReady is supported on all AndroidTV devices. Note that most other Android devices do not * provide PlayReady support. */ - val PLAYREADY_UUID = UUID(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) + val PLAYREADY_UUID = Uuid.fromLongs(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) /** "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. */ @@ -1066,7 +1067,7 @@ object HlsPlaylistParser { object PsshAtomUtil { fun buildPsshAtom( - systemId: UUID, keyIds: Array?, data: ByteArray? + systemId: Uuid, keyIds: Array?, data: ByteArray? ): ByteArray { val dataLength = data?.size ?: 0 var psshBoxLength: Int = @@ -1074,26 +1075,30 @@ object HlsPlaylistParser { if (keyIds != null) { psshBoxLength += 4 /* KID_count */ + (keyIds.size * 16) /* KIDs */ } - val psshBox: ByteBuffer = ByteBuffer.allocate(psshBoxLength) - psshBox.putInt(psshBoxLength) - psshBox.putInt(Mp4Box.TYPE_pssh) - psshBox.putInt(if (keyIds != null) 0x01000000 else 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */) - psshBox.putLong(systemId.mostSignificantBits) - psshBox.putLong(systemId.leastSignificantBits) + val buffer = Buffer() + buffer.writeInt(psshBoxLength) + buffer.writeInt(Mp4Box.TYPE_pssh) + buffer.writeInt(if (keyIds != null) 0x01000000 else 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */) + systemId.toLongs { mostSignificantBits, leastSignificantBits -> + buffer.writeLong(mostSignificantBits) + buffer.writeLong(leastSignificantBits) + } if (keyIds != null) { - psshBox.putInt(keyIds.size) + buffer.writeInt(keyIds.size) for (keyId in keyIds) { - psshBox.putLong(keyId.mostSignificantBits) - psshBox.putLong(keyId.leastSignificantBits) + keyId.toLongs { mostSignificantBits, leastSignificantBits -> + buffer.writeLong(mostSignificantBits) + buffer.writeLong(leastSignificantBits) + } } } if (data != null && data.size != 0) { - psshBox.putInt(data.size) - psshBox.put(data) + buffer.writeInt(data.size) + buffer.write(data) } else { - psshBox.putInt(0) + buffer.writeInt(0) } - return psshBox.array() + return buffer.readByteArray() } } diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExtTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExtTest.kt new file mode 100644 index 000000000..970fe728d --- /dev/null +++ b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExtTest.kt @@ -0,0 +1,31 @@ +package com.lagradost.cloudstream3.mvvm + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ArchComponentExtTest { + + @Test + fun getStackTracePrettyStripsFullyQualifiedClassNames() { + val throwable = try { + throw IllegalStateException("Exception") + } catch (t: Throwable) { + t + } + + val pretty = throwable.getStackTracePretty() + + // Should not contain the fully qualified package/class prefix + assertFalse( + pretty.contains("com.lagradost.cloudstream3.mvvm.ArchComponentExtTest"), + "Expected no fully qualified class names, got:\n$pretty" + ) + + // Should still reference the source file itself + assertTrue( + pretty.contains("ArchComponentExtTest.kt"), + "Expected pretty stack trace to reference a .kt file, got:\n$pretty" + ) + } +} diff --git a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvmCommon.kt b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvmCommon.kt new file mode 100644 index 000000000..f2ff5e3ac --- /dev/null +++ b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvmCommon.kt @@ -0,0 +1,37 @@ +package com.lagradost.cloudstream3.mvvm + +import java.io.InterruptedIOException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import javax.net.ssl.SSLHandshakeException +import kotlin.reflect.full.NoSuchPropertyException + +actual fun platformThrowAbleToResource(throwable: Throwable): Resource { + return when (throwable) { + is NoSuchMethodException, is NoSuchFieldException, is NoSuchMethodError, is NoSuchFieldError, is NoSuchPropertyException -> { + Resource.Failure( + false, + "App or extension is outdated, update the app or try pre-release.\n${throwable.message}" // todo add exact version? + ) + } + is SocketTimeoutException, is InterruptedIOException -> { + Resource.Failure( + true, + "Connection Timeout\nPlease try again later." + ) + } + is UnknownHostException -> { + Resource.Failure( + true, + "Cannot connect to server, try again later.\n${throwable.message}" + ) + } + is SSLHandshakeException -> { + Resource.Failure( + true, + (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS." + ) + } + else -> safeFail(throwable) + } +} diff --git a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt index 2ae886f59..03a5be6c2 100644 --- a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt +++ b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt @@ -1,3 +1,7 @@ package com.lagradost.cloudstream3.utils +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +actual val workerDispatcher: CoroutineDispatcher = Dispatchers.IO internal actual typealias WorkerThread = androidx.annotation.WorkerThread diff --git a/library/src/webMain/kotlin/com/lagradost/api/ContextHelper.web.kt b/library/src/webMain/kotlin/com/lagradost/api/ContextHelper.web.kt new file mode 100644 index 000000000..83cae11a5 --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/api/ContextHelper.web.kt @@ -0,0 +1,4 @@ +package com.lagradost.api + +actual fun getContext(): Any? = null +actual fun setContext(context: Any?) = Unit diff --git a/library/src/webMain/kotlin/com/lagradost/api/Log.kt b/library/src/webMain/kotlin/com/lagradost/api/Log.kt new file mode 100644 index 000000000..be185f0ca --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/api/Log.kt @@ -0,0 +1,21 @@ +package com.lagradost.api + +actual object Log { + actual fun d(tag: String, message: String) { + // There is no console.debug in Kotlin/JS (Yet) + // We will switch this when it is added. + console.log("[$tag] $message") + } + + actual fun i(tag: String, message: String) { + console.info("[$tag] $message") + } + + actual fun w(tag: String, message: String) { + console.warn("[$tag] $message") + } + + actual fun e(tag: String, message: String) { + console.error("[$tag] $message") + } +} diff --git a/library/src/webMain/kotlin/com/lagradost/cloudstream3/extractors/YoutubeExtractor.web.kt b/library/src/webMain/kotlin/com/lagradost/cloudstream3/extractors/YoutubeExtractor.web.kt new file mode 100644 index 000000000..07463bd56 --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/cloudstream3/extractors/YoutubeExtractor.web.kt @@ -0,0 +1,32 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink + +/** + * Web/JS target shim. + * + * NewPipeExtractor is JVM-only, so the full extraction logic used on + * Android/desktop can't run here. This stub keeps the multiplatform + * source set compiling; it performs no extraction and simply reports + * that no links were found on this target. + * + * If/when a JS-compatible YouTube extraction path exists, replace the + * body of getUrl() with that implementation. + */ +actual open class YoutubeExtractor actual constructor() : ExtractorApi() { + + actual override val mainUrl = "https://www.youtube.com" + actual override val name = "YouTube" + actual override val requiresReferer = false + + actual override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit, + ) { + // No-op on web target: NewPipeExtractor is unavailable here. + } +} diff --git a/library/src/webMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.web.kt b/library/src/webMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.web.kt new file mode 100644 index 000000000..f108bf7fb --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.web.kt @@ -0,0 +1,32 @@ +package com.lagradost.cloudstream3.mvvm + +actual fun platformThrowAbleToResource(throwable: Throwable): Resource { + val message = throwable.message.orEmpty() + return when { + message.contains("timeout", ignoreCase = true) -> { + Resource.Failure( + true, + "Connection Timeout\nPlease try again later." + ) + } + + message.contains("NetworkError", ignoreCase = true) || + message.contains("Failed to fetch", ignoreCase = true) || + message.contains("ERR_NAME_NOT_RESOLVED", ignoreCase = true) -> { + Resource.Failure( + true, + "Cannot connect to server, try again later.\n${throwable.message}" + ) + } + + message.contains("SSL", ignoreCase = true) || + message.contains("certificate", ignoreCase = true) -> { + Resource.Failure( + true, + (throwable.message ?: "SSL error") + "\nTry a VPN or DNS." + ) + } + + else -> safeFail(throwable) + } +} diff --git a/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.web.kt b/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.web.kt new file mode 100644 index 000000000..3ea59f6cc --- /dev/null +++ b/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.web.kt @@ -0,0 +1,14 @@ +package com.lagradost.cloudstream3.utils + +import androidx.annotation.AnyThread +import androidx.annotation.MainThread +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +actual val workerDispatcher: CoroutineDispatcher = Dispatchers.Default +internal actual annotation class WorkerThread() + +@AnyThread +actual fun runOnMainThreadNative(@MainThread work: () -> Unit) { + work.invoke() +} diff --git a/library/src/webTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.web.kt b/library/src/webTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.web.kt new file mode 100644 index 000000000..0381a1944 --- /dev/null +++ b/library/src/webTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.web.kt @@ -0,0 +1,5 @@ +package com.lagradost.cloudstream3.utils + +import kotlin.test.Ignore + +actual typealias IgnoreOnWeb = Ignore