From 77294dc68e6ba19c21017f4ac17847755572ad18 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Sat, 7 Oct 2023 01:39:30 +0200 Subject: [PATCH] cleanup --- .../lagradost/cloudstream3/MainActivity.kt | 40 +++--- .../cloudstream3/ui/home/HomeFragment.kt | 25 +--- .../cloudstream3/ui/home/HomeViewModel.kt | 22 ++- .../ui/player/AbstractPlayerFragment.kt | 11 +- .../ui/player/FullScreenPlayer.kt | 6 +- .../cloudstream3/ui/player/IPlayer.kt | 9 -- .../ui/player/PreviewGenerator.kt | 134 ++++++++++++------ .../cloudstream3/ui/search/SearchFragment.kt | 43 ++---- .../cloudstream3/utils/DataStoreHelper.kt | 68 +++++++++ .../main/res/layout/player_custom_layout.xml | 4 +- 10 files changed, 215 insertions(+), 147 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index d5187029..17823f7c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -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) } 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 ebbb245c..4d940123 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 @@ -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() @@ -377,10 +359,7 @@ class HomeFragment : Fragment() { var currentApiName = selectedApiName var currentValidApis: MutableList = mutableListOf() - val preSelectedTypes = this.getKey>("${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 -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index a5ef2bb4..ad75aa9d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -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() 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("${DataStoreHelper.currentAccount}/$HOME_BOOKMARK_VALUE_LIST")?.map { WatchType.fromInternalId(it) }?.let { + DataStoreHelper.homeBookmarkedList.map { WatchType.fromInternalId(it) }.let { list.addAll(it) } loadStoredData(list) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index 52974ff7..431e4fe1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -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 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 43e8aa0b..819e50ba 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 @@ -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 } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt index a08360ae..0e54e2cb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IPlayer.kt @@ -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 { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt index 946c1d33..ffb4751f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt @@ -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 = arrayOf() @@ -118,7 +154,7 @@ class M3u8PreviewGenerator : IPreviewGenerator { private var prefixSum: Array = 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) { - clear(keepCache) + fun load(url: String, headers: Map) { + 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((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) { + fun load(url: String, headers: Map) { 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) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index bad78624..0e994be8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -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.filterSearchResponse(): List { @@ -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) { - 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>("$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>("$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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 775cb718..10c0546f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -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( + private val key: String, private val default: T //, private val klass: KClass +) { + private val klass: KClass = 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 by UserPreferenceDelegate( + /** java moment right here, as listOf()::class.java != List(0) { "" }::class.java */ + "search_pref_providers", List(0) { "" } + ) + + private fun serializeTv(data : List) : List = data.map { it.name } + + private fun deserializeTv(data : List) : List { + return data.mapNotNull { listName -> + TvType.values().firstOrNull { it.name == listName } + } + } + + var searchPreferenceProviders : List + get() { + val ret = searchPreferenceProvidersStrings + return ret.ifEmpty { + context?.filterProviderByPreferredMedia()?.map { it.name } ?: emptyList() + } + } set(value) { + searchPreferenceProvidersStrings = value + } + + private var searchPreferenceTagsStrings : List by UserPreferenceDelegate("search_pref_tags", listOf(TvType.Movie, TvType.TvSeries).map { it.name }) + var searchPreferenceTags : List + get() = deserializeTv(searchPreferenceTagsStrings) + set(value) { + searchPreferenceTagsStrings = serializeTv(value) + } + + + private var homePreferenceStrings : List by UserPreferenceDelegate("home_pref_homepage", listOf(TvType.Movie, TvType.TvSeries).map { it.name }) + var homePreference : List + 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, diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index 0f76e4dd..38df4c5b 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -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">