This commit is contained in:
LagradOst 2023-10-07 01:39:30 +02:00
parent 0a327ccbda
commit 77294dc68e
10 changed files with 215 additions and 147 deletions

View file

@ -1122,23 +1122,25 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
if (isTvSettings()) {
val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false)
setContentView(newLocalBinding.root)
TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
// println("refocus $oldFocus -> $newFocus")
try {
val r = Rect(0, 0, 0, 0)
newFocus.getDrawingRect(r)
val x = r.centerX()
val y = r.centerY()
val dx = 0 //screenWidth / 2
val dy = screenHeight / 2
val r2 = Rect(x - dx, y - dy, x + dx, y + dy)
newFocus.requestRectangleOnScreen(r2, false)
// TvFocus.current =TvFocus.current.copy(y=y.toFloat())
} catch (_: Throwable) {
}
TvFocus.updateFocusView(newFocus)
/*var focus = newFocus
if(isTrueTvSettings()) {
TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
// println("refocus $oldFocus -> $newFocus")
try {
val r = Rect(0, 0, 0, 0)
newFocus.getDrawingRect(r)
val x = r.centerX()
val y = r.centerY()
val dx = 0 //screenWidth / 2
val dy = screenHeight / 2
val r2 = Rect(x - dx, y - dy, x + dx, y + dy)
newFocus.requestRectangleOnScreen(r2, false)
// TvFocus.current =TvFocus.current.copy(y=y.toFloat())
} catch (_: Throwable) {
}
TvFocus.updateFocusView(newFocus)
/*var focus = newFocus
while(focus != null) {
if(focus is ScrollingView && focus.canScrollVertically()) {
@ -1149,7 +1151,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
else -> break
}
}*/
}
} else {
newLocalBinding.focusOutline.isVisible = false
}
newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener {
TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true)
}

View file

@ -7,7 +7,6 @@ import android.content.DialogInterface
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -23,18 +22,12 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.MainActivity.Companion.bookmarksUpdatedEvent
import com.lagradost.cloudstream3.MainActivity.Companion.mainPluginsLoadedEvent
import com.lagradost.cloudstream3.databinding.FragmentHomeBinding
import com.lagradost.cloudstream3.databinding.HomeEpisodesExpandedBinding
import com.lagradost.cloudstream3.databinding.HomeSelectMainpageBinding
@ -45,37 +38,26 @@ import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.search.*
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppUtils.ownHide
import com.lagradost.cloudstream3.utils.AppUtils.ownShow
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import java.util.*
const val HOME_BOOKMARK_VALUE_LIST = "home_bookmarked_last_list"
const val HOME_PREF_HOMEPAGE = "home_pref_homepage"
class HomeFragment : Fragment() {
companion object {
val configEvent = Event<Int>()
@ -377,10 +359,7 @@ class HomeFragment : Fragment() {
var currentApiName = selectedApiName
var currentValidApis: MutableList<MainAPI> = mutableListOf()
val preSelectedTypes = this.getKey<List<String>>("${DataStoreHelper.currentAccount}/$HOME_PREF_HOMEPAGE")
?.mapNotNull { listName -> TvType.values().firstOrNull { it.name == listName } }
?.toMutableList()
?: mutableListOf(TvType.Movie, TvType.TvSeries)
val preSelectedTypes = DataStoreHelper.homePreference.toMutableList()
binding.cancelBtt.setOnClickListener {
dialog.dismissSafe()
@ -408,7 +387,7 @@ class HomeFragment : Fragment() {
}
fun updateList() {
this.setKey("${DataStoreHelper.currentAccount}/$HOME_PREF_HOMEPAGE", preSelectedTypes)
DataStoreHelper.homePreference = preSelectedTypes
arrayAdapter.clear()
currentValidApis = validAPIs.filter { api ->

View file

@ -12,7 +12,6 @@ import com.lagradost.cloudstream3.APIHolder.filterSearchResultByFilmQuality
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.activity
import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.LoadResponse
@ -170,10 +169,7 @@ class HomeViewModel : ViewModel() {
currentWatchTypes.remove(WatchType.NONE)
if (currentWatchTypes.size <= 0) {
setKey(
"${DataStoreHelper.currentAccount}/$HOME_BOOKMARK_VALUE_LIST",
intArrayOf()
)
DataStoreHelper.homeBookmarkedList = intArrayOf()
_availableWatchStatusTypes.postValue(setOf<WatchType>() to setOf())
_bookmarks.postValue(Pair(false, ArrayList()))
return@launchSafe
@ -181,16 +177,14 @@ class HomeViewModel : ViewModel() {
val watchPrefNotNull = preferredWatchStatus ?: EnumSet.of(currentWatchTypes.first())
//if (currentWatchTypes.any { watchPrefNotNull.contains(it) }) watchPrefNotNull else listOf(currentWatchTypes.first())
setKey(
"${DataStoreHelper.currentAccount}/$HOME_BOOKMARK_VALUE_LIST",
watchPrefNotNull.map { it.internalId }.toIntArray()
)
DataStoreHelper.homeBookmarkedList = watchPrefNotNull.map { it.internalId }.toIntArray()
_availableWatchStatusTypes.postValue(
Pair(
watchPrefNotNull,
currentWatchTypes,
watchPrefNotNull to
currentWatchTypes,
)
)
val list = withContext(Dispatchers.IO) {
watchStatusIds.filter { watchPrefNotNull.contains(it.second) }
@ -463,7 +457,7 @@ class HomeViewModel : ViewModel() {
fun loadStoredData() {
val list = EnumSet.noneOf(WatchType::class.java)
getKey<IntArray>("${DataStoreHelper.currentAccount}/$HOME_BOOKMARK_VALUE_LIST")?.map { WatchType.fromInternalId(it) }?.let {
DataStoreHelper.homeBookmarkedList.map { WatchType.fromInternalId(it) }.let {
list.addAll(it)
}
loadStoredData(list)

View file

@ -33,8 +33,6 @@ import androidx.preference.PreferenceManager
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.github.rubensousa.previewseekbar.PreviewBar
import com.github.rubensousa.previewseekbar.media3.PreviewTimeBar
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.canEnterPipMode
import com.lagradost.cloudstream3.CommonActivity.isInPIPMode
import com.lagradost.cloudstream3.CommonActivity.keyEventListener
@ -48,7 +46,7 @@ import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.AppUtils.requestLocalAudioFocus
import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.EpisodeSkip
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
@ -443,7 +441,7 @@ abstract class AbstractPlayerFragment(
@SuppressLint("SetTextI18n", "UnsafeOptInUsageError")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
resizeMode = getKey("$currentAccount/$RESIZE_MODE_KEY") ?: 0
resizeMode = DataStoreHelper.resizeMode
resize(resizeMode, false)
player.releaseCallbacks()
@ -466,7 +464,8 @@ abstract class AbstractPlayerFragment(
var resume = false
progressBar.addOnScrubListener(object : PreviewBar.OnScrubListener {
override fun onScrubStart(previewBar: PreviewBar?) {
progressBar.isPreviewEnabled = player.hasPreview()
val hasPreview = player.hasPreview()
progressBar.isPreviewEnabled = hasPreview
resume = player.getIsPlaying()
if (resume) player.handleEvent(
CSPlayerEvent.Pause,
@ -574,7 +573,7 @@ abstract class AbstractPlayerFragment(
@SuppressLint("UnsafeOptInUsageError")
fun resize(resize: PlayerResize, showToast: Boolean) {
setKey("$currentAccount/$RESIZE_MODE_KEY", resize.ordinal)
DataStoreHelper.resizeMode = resize.ordinal
val type = when (resize) {
PlayerResize.Fill -> AspectRatioFrameLayout.RESIZE_MODE_FILL
PlayerResize.Fit -> AspectRatioFrameLayout.RESIZE_MODE_FIT

View file

@ -49,7 +49,7 @@ import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
@ -357,7 +357,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
private fun setPlayBackSpeed(speed: Float) {
try {
setKey("$currentAccount/$PLAYBACK_SPEED_KEY", speed)
DataStoreHelper.playBackSpeed = speed
playerBinding?.playerSpeedBtt?.text =
getString(R.string.player_speed_text_format).format(speed)
.replace(".0x", "x")
@ -1195,7 +1195,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// init variables
setPlayBackSpeed(getKey("$currentAccount/$PLAYBACK_SPEED_KEY") ?: 1.0f)
setPlayBackSpeed(DataStoreHelper.playBackSpeed)
savedInstanceState?.getLong(SUBTITLE_DELAY_BUNDLE_KEY)?.let {
subtitleDelay = it
}

View file

@ -199,17 +199,8 @@ data class CurrentTracks(
class InvalidFileException(msg: String) : Exception(msg)
//http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
const val STATE_RESUME_WINDOW = "resumeWindow"
const val STATE_RESUME_POSITION = "resumePosition"
const val STATE_PLAYER_FULLSCREEN = "playerFullscreen"
const val STATE_PLAYER_PLAYING = "playerOnPlay"
const val ACTION_MEDIA_CONTROL = "media_control"
const val EXTRA_CONTROL_TYPE = "control_type"
const val PLAYBACK_SPEED = "playback_speed"
const val RESIZE_MODE_KEY = "resize_mode" // Last used resize mode
const val PLAYBACK_SPEED_KEY = "playback_speed" // Last used playback speed
const val PREFERRED_SUBS_KEY = "preferred_subtitles" // Last used resize mode
//const val PLAYBACK_FASTFORWARD = "playback_fastforward" // Last used resize mode
/** Abstract Exoplayer logic, can be expanded to other players */
interface IPlayer {

View file

@ -28,86 +28,122 @@ const val MIN_LOD = 3
interface IPreviewGenerator {
fun hasPreview(): Boolean
fun getPreviewImage(fraction: Float): Bitmap?
fun clear(keepCache: Boolean = false)
fun release()
var durationMs: Long
var loadedImages: Int
}
/** PreviewGenerator that hides the implementation details of the sub generators that is used, used for source switch cache */
class PreviewGenerator : IPreviewGenerator {
/** the most up to date generator, will always mirror the actual source in the player */
private var currentGenerator: IPreviewGenerator = NoPreviewGenerator()
/** the longest generated preview of the same episode */
private var lastGenerator: IPreviewGenerator = NoPreviewGenerator()
/** always NoPreviewGenerator, used as a cache for nothing */
private val dummy: IPreviewGenerator = NoPreviewGenerator()
/** if the current generator is the same as the last by checking time */
private fun isSameLength(): Boolean =
currentGenerator.durationMs.minus(lastGenerator.durationMs).absoluteValue < 10_000L
/** use the backup if the current generator is init or if they have the same length */
private val backupGenerator: IPreviewGenerator
get() {
if (currentGenerator.durationMs == 0L || isSameLength()) {
return lastGenerator
}
return dummy
}
override fun hasPreview(): Boolean {
return currentGenerator.hasPreview()
return currentGenerator.hasPreview() || backupGenerator.hasPreview()
}
override fun getPreviewImage(fraction: Float): Bitmap? {
return try {
currentGenerator.getPreviewImage(fraction)
currentGenerator.getPreviewImage(fraction) ?: backupGenerator.getPreviewImage(fraction)
} catch (t: Throwable) {
logError(t)
null
}
}
override fun clear(keepCache: Boolean) {
currentGenerator.clear(keepCache)
override fun release() {
lastGenerator.release()
currentGenerator.release()
lastGenerator = NoPreviewGenerator()
currentGenerator = NoPreviewGenerator()
}
override fun release() {
currentGenerator.release()
override var durationMs: Long
get() = currentGenerator.durationMs
set(_) {}
override var loadedImages: Int
get() = currentGenerator.loadedImages
set(_) {}
fun clear(keepCache: Boolean) {
if (keepCache) {
if (!isSameLength() || currentGenerator.loadedImages >= lastGenerator.loadedImages || lastGenerator.durationMs == 0L) {
// the current generator is better than the last generator, therefore keep the current
// or the lengths are not the same, therefore favoring the more recent selection
// if they are the same we favor the current generator
lastGenerator.release()
lastGenerator = currentGenerator
} else {
// otherwise just keep the last generator and throw away the current generator
currentGenerator.release()
}
} else {
// we switched the episode, therefore keep nothing
lastGenerator.release()
lastGenerator = NoPreviewGenerator()
currentGenerator.release()
// we assume that we set currentGenerator right after this, so currentGenerator != NoPreviewGenerator
}
}
fun load(link: ExtractorLink, keepCache: Boolean) {
val gen = currentGenerator
clear(keepCache)
when (link.type) {
ExtractorLinkType.M3U8 -> {
if (gen is M3u8PreviewGenerator) {
gen.load(keepCache = keepCache, url = link.url, headers = link.getAllHeaders())
} else {
currentGenerator.release()
currentGenerator = M3u8PreviewGenerator().apply {
load(keepCache = keepCache, url = link.url, headers = link.getAllHeaders())
}
currentGenerator = M3u8PreviewGenerator().apply {
load(url = link.url, headers = link.getAllHeaders())
}
}
ExtractorLinkType.VIDEO -> {
if (gen is Mp4PreviewGenerator) {
gen.load(keepCache = keepCache, url = link.url, headers = link.getAllHeaders())
} else {
currentGenerator.release()
currentGenerator = Mp4PreviewGenerator().apply {
load(keepCache = keepCache, url = link.url, headers = link.getAllHeaders())
}
currentGenerator = Mp4PreviewGenerator().apply {
load(url = link.url, headers = link.getAllHeaders())
}
}
else -> {
Log.i("PreviewImg", "unsupported format for $link")
currentGenerator.clear(keepCache)
}
}
}
fun load(context: Context, link: ExtractorUri, keepCache: Boolean) {
val gen = currentGenerator
if (gen is Mp4PreviewGenerator) {
gen.load(keepCache = keepCache, context = context, uri = link.uri)
} else {
currentGenerator.release()
currentGenerator = Mp4PreviewGenerator().apply {
load(keepCache = keepCache, context = context, uri = link.uri)
}
clear(keepCache)
currentGenerator = Mp4PreviewGenerator().apply {
load(keepCache = keepCache, context = context, uri = link.uri)
}
}
}
class NoPreviewGenerator : IPreviewGenerator {
private class NoPreviewGenerator : IPreviewGenerator {
override fun hasPreview(): Boolean = false
override fun getPreviewImage(fraction: Float): Bitmap? = null
override fun clear(keepCache: Boolean) = Unit
override fun release() = Unit
override var durationMs: Long = 0L
override var loadedImages: Int = 0
}
class M3u8PreviewGenerator : IPreviewGenerator {
private class M3u8PreviewGenerator : IPreviewGenerator {
// generated images 1:1 to idx of hsl
private var images: Array<Bitmap?> = arrayOf()
@ -118,7 +154,7 @@ class M3u8PreviewGenerator : IPreviewGenerator {
private var prefixSum: Array<Double> = arrayOf()
// how many images has been generated
private var loadedImages: Int = 0
override var loadedImages: Int = 0
// how many images we can generate in total, == hsl.size ?: 0
private var totalImages: Int = 0
@ -155,7 +191,7 @@ class M3u8PreviewGenerator : IPreviewGenerator {
}*/
}
override fun clear(keepCache: Boolean) {
private fun clear() {
synchronized(images) {
currentJob?.cancel()
images = arrayOf()
@ -170,9 +206,11 @@ class M3u8PreviewGenerator : IPreviewGenerator {
images = arrayOf()
}
override var durationMs: Long = 0L
private var currentJob: Job? = null
fun load(keepCache: Boolean, url: String, headers: Map<String, String>) {
clear(keepCache)
fun load(url: String, headers: Map<String, String>) {
clear()
currentJob?.cancel()
currentJob = ioSafe {
withContext(Dispatchers.IO) {
@ -201,6 +239,7 @@ class M3u8PreviewGenerator : IPreviewGenerator {
// total duration of the entire m3u8 in seconds
val duration = hsl.allTsLinks.sumOf { it.time ?: 0.0 }
durationMs = (duration * 1000.0).toLong()
val durationInv = 1.0 / duration
// if the total duration is less then 10s then something is very wrong or
@ -245,7 +284,7 @@ class M3u8PreviewGenerator : IPreviewGenerator {
if (!isActive) {
return@withContext
}
if(img == null || img.width <= 1 || img.height <= 1) continue
if (img == null || img.width <= 1 || img.height <= 1) continue
synchronized(images) {
images[index] = img
loadedImages += 1
@ -269,11 +308,11 @@ class M3u8PreviewGenerator : IPreviewGenerator {
}
}
class Mp4PreviewGenerator : IPreviewGenerator {
private class Mp4PreviewGenerator : IPreviewGenerator {
// lod = level of detail where the number indicates how many ones there is
// 2^(lod-1) = images
private var loadedLod = 0
private var loadedImages = 0
override var loadedImages = 0
private var images = Array<Bitmap?>((1 shl MAX_LOD) - 1) {
null
}
@ -305,7 +344,7 @@ class Mp4PreviewGenerator : IPreviewGenerator {
if (idx > loadedImages) {
break
}
if(images[idx] == null) {
if (images[idx] == null) {
continue
}
val currentFraction =
@ -325,7 +364,7 @@ class Mp4PreviewGenerator : IPreviewGenerator {
// also check out https://github.com/wseemann/FFmpegMediaMetadataRetriever
private val retriever: MediaMetadataRetriever = MediaMetadataRetriever()
override fun clear(keepCache: Boolean) {
private fun clear(keepCache: Boolean) {
if (keepCache) return
synchronized(images) {
loadedLod = 0
@ -335,11 +374,11 @@ class Mp4PreviewGenerator : IPreviewGenerator {
}
private var currentJob: Job? = null
fun load(keepCache: Boolean, url: String, headers: Map<String, String>) {
fun load(url: String, headers: Map<String, String>) {
currentJob?.cancel()
currentJob = ioSafe {
Log.i(TAG, "Loading with url = $url headers = $headers")
clear(keepCache)
clear(true)
retriever.setDataSource(url, headers)
start(this)
}
@ -360,6 +399,8 @@ class Mp4PreviewGenerator : IPreviewGenerator {
clear(false)
}
override var durationMs: Long = 0L
@Throws
@WorkerThread
private fun start(scope: CoroutineScope) {
@ -368,6 +409,7 @@ class Mp4PreviewGenerator : IPreviewGenerator {
val durationMs =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong()
?: throw IllegalArgumentException("Bad video duration")
this.durationMs = durationMs
val durationUs = (durationMs * 1000L).toFloat()
//val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toInt() ?: throw IllegalArgumentException("Bad video width")
//val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toInt() ?: throw IllegalArgumentException("Bad video height")
@ -388,7 +430,7 @@ class Mp4PreviewGenerator : IPreviewGenerator {
MediaMetadataRetriever.OPTION_CLOSEST_SYNC
)
if (!scope.isActive) return
if(img == null || img.width <= 1 || img.height <= 1) continue
if (img == null || img.width <= 1 || img.height <= 1) continue
synchronized(images) {
images[idx] = img
loadedImages = maxOf(loadedImages, idx)

View file

@ -22,17 +22,22 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
import com.lagradost.cloudstream3.APIHolder.filterSearchResultByFilmQuality
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.APIHolder.getApiSettings
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.AllLanguagesName
import com.lagradost.cloudstream3.AnimeSearchResponse
import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.databinding.FragmentSearchBinding
import com.lagradost.cloudstream3.databinding.HomeSelectMainpageBinding
import com.lagradost.cloudstream3.mvvm.Resource
@ -53,8 +58,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.ownHide
import com.lagradost.cloudstream3.utils.AppUtils.ownShow
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
@ -63,9 +67,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import java.util.concurrent.locks.ReentrantLock
const val SEARCH_PREF_TAGS = "search_pref_tags"
const val SEARCH_PREF_PROVIDERS = "search_pref_providers"
class SearchFragment : Fragment() {
companion object {
fun List<SearchResponse>.filterSearchResponse(): List<SearchResponse> {
@ -194,7 +195,7 @@ class SearchFragment : Fragment() {
validAPIs.flatMap { api -> api.supportedTypes }.distinct()
) { list ->
if (selectedSearchTypes.toSet() != list.toSet()) {
setKey("$currentAccount/$SEARCH_PREF_TAGS", selectedSearchTypes)
DataStoreHelper.searchPreferenceTags = list
selectedSearchTypes.clear()
selectedSearchTypes.addAll(list)
search(binding?.mainSearch?.query?.toString())
@ -233,13 +234,7 @@ class SearchFragment : Fragment() {
//searchMagIcon.scaleX = 0.65f
//searchMagIcon.scaleY = 0.65f
context?.let { ctx ->
val validAPIs = ctx.filterProviderByPreferredMedia()
selectedApis = ctx.getKey(
"$currentAccount/$SEARCH_PREF_PROVIDERS",
defVal = validAPIs.map { it.name }
)!!.toMutableSet()
}
selectedApis = DataStoreHelper.searchPreferenceProviders.toMutableSet()
binding?.searchFilter?.setOnClickListener { searchView ->
searchView?.context?.let { ctx ->
@ -287,7 +282,7 @@ class SearchFragment : Fragment() {
}
fun updateList(types: List<TvType>) {
setKey("$currentAccount/$SEARCH_PREF_TAGS", types.map { it.name })
DataStoreHelper.searchPreferenceTags = types
arrayAdapter.clear()
currentValidApis = validAPIs.filter { api ->
@ -312,12 +307,7 @@ class SearchFragment : Fragment() {
arrayAdapter.notifyDataSetChanged()
}
val selectedSearchTypes = getKey<List<String>>("$currentAccount/$SEARCH_PREF_TAGS")
?.mapNotNull { listName ->
TvType.values().firstOrNull { it.name == listName }
}
?.toMutableList()
?: mutableListOf(TvType.Movie, TvType.TvSeries)
val selectedSearchTypes = DataStoreHelper.searchPreferenceTags
bindChips(
binding.tvtypesChipsScroll.tvtypesChips,
@ -343,7 +333,7 @@ class SearchFragment : Fragment() {
}
dialog.setOnDismissListener {
context?.setKey("$currentAccount/$SEARCH_PREF_PROVIDERS", currentSelectedApis.toList())
DataStoreHelper.searchPreferenceProviders = currentSelectedApis.toList()
selectedApis = currentSelectedApis
}
updateList(selectedSearchTypes.toList())
@ -354,10 +344,7 @@ class SearchFragment : Fragment() {
val settingsManager = context?.let { PreferenceManager.getDefaultSharedPreferences(it) }
val isAdvancedSearch = settingsManager?.getBoolean("advanced_search", true) ?: true
selectedSearchTypes = context?.getKey<List<String>>("$currentAccount/$SEARCH_PREF_TAGS")
?.mapNotNull { listName -> TvType.values().firstOrNull { it.name == listName } }
?.toMutableList()
?: mutableListOf(TvType.Movie, TvType.TvSeries)
selectedSearchTypes = DataStoreHelper.searchPreferenceTags.toMutableList()
if (isTrueTvSettings()) {
binding?.searchFilter?.isFocusable = true

View file

@ -10,7 +10,9 @@ import androidx.core.widget.doOnTextChanged
import com.fasterxml.jackson.annotation.JsonProperty
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
@ -31,6 +33,8 @@ import com.lagradost.cloudstream3.ui.result.setImage
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
const val VIDEO_POS_DUR = "video_pos_dur"
const val VIDEO_WATCH_STATE = "video_watch_state"
@ -44,6 +48,28 @@ const val RESULT_EPISODE = "result_episode"
const val RESULT_SEASON = "result_season"
const val RESULT_DUB = "result_dub"
class UserPreferenceDelegate<T : Any>(
private val key: String, private val default: T //, private val klass: KClass<T>
) {
private val klass: KClass<out T> = default::class
private val realKey get() = "${DataStoreHelper.currentAccount}/$key"
operator fun getValue(self: Any?, property: KProperty<*>) =
AcraApplication.getKeyClass(realKey, klass.java) ?: default
operator fun setValue(
self: Any?,
property: KProperty<*>,
t: T?
) {
if (t == null) {
removeKey(realKey)
} else {
AcraApplication.setKeyClass(realKey, t)
}
}
}
object DataStoreHelper {
// be aware, don't change the index of these as Account uses the index for the art
private val profileImages = arrayOf(
@ -56,6 +82,48 @@ object DataStoreHelper {
R.drawable.profile_bg_teal
)
private var searchPreferenceProvidersStrings : List<String> by UserPreferenceDelegate(
/** java moment right here, as listOf()::class.java != List(0) { "" }::class.java */
"search_pref_providers", List(0) { "" }
)
private fun serializeTv(data : List<TvType>) : List<String> = data.map { it.name }
private fun deserializeTv(data : List<String>) : List<TvType> {
return data.mapNotNull { listName ->
TvType.values().firstOrNull { it.name == listName }
}
}
var searchPreferenceProviders : List<String>
get() {
val ret = searchPreferenceProvidersStrings
return ret.ifEmpty {
context?.filterProviderByPreferredMedia()?.map { it.name } ?: emptyList()
}
} set(value) {
searchPreferenceProvidersStrings = value
}
private var searchPreferenceTagsStrings : List<String> by UserPreferenceDelegate("search_pref_tags", listOf(TvType.Movie, TvType.TvSeries).map { it.name })
var searchPreferenceTags : List<TvType>
get() = deserializeTv(searchPreferenceTagsStrings)
set(value) {
searchPreferenceTagsStrings = serializeTv(value)
}
private var homePreferenceStrings : List<String> by UserPreferenceDelegate("home_pref_homepage", listOf(TvType.Movie, TvType.TvSeries).map { it.name })
var homePreference : List<TvType>
get() = deserializeTv(homePreferenceStrings)
set(value) {
homePreferenceStrings = serializeTv(value)
}
var homeBookmarkedList : IntArray by UserPreferenceDelegate("home_bookmarked_last_list", IntArray(0))
var playBackSpeed : Float by UserPreferenceDelegate("playback_speed", 1.0f)
var resizeMode : Int by UserPreferenceDelegate("resize_mode", 0)
data class Account(
@JsonProperty("keyIndex")
val keyIndex: Int,

View file

@ -349,14 +349,15 @@
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_marginEnd="100dp"
android:layout_marginTop="60dp"
android:backgroundTint="@color/skipOpTransparent"
android:maxLines="1"
android:padding="10dp"
android:textColor="@color/white"
android:visibility="gone"
app:cornerRadius="@dimen/rounded_button_radius"
app:layout_constraintBottom_toTopOf="@+id/bottom_player_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/player_top_holder"
app:strokeColor="@color/white"
app:strokeWidth="1dp"
tools:text="Skip Opening"
@ -435,6 +436,7 @@
android:id="@+id/player_video_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:visibility="visible"
android:layoutDirection="ltr"
android:orientation="horizontal">