From 3f8229756de7d4abb5a6db15b439f9522eb26779 Mon Sep 17 00:00:00 2001 From: LagradOst Date: Wed, 4 Aug 2021 03:50:24 +0200 Subject: [PATCH] subs --- .../com/lagradost/cloudstream3/MainAPI.kt | 19 +- .../lagradost/cloudstream3/MainActivity.kt | 8 +- .../movieproviders/MeloMovieProvider.kt | 10 +- .../movieproviders/TrailersToProvider.kt | 5 +- .../cloudstream3/mvvm/ArchComponentExt.kt | 9 +- .../cloudstream3/ui/APIRepository.kt | 10 +- .../cloudstream3/ui/ControllerActivity.kt | 11 +- .../cloudstream3/ui/player/PlayerFragment.kt | 245 +++++++++++++++--- .../cloudstream3/ui/result/EpisodeAdapter.kt | 21 +- .../lagradost/cloudstream3/utils/AppUtils.kt | 8 - .../main/res/color/check_selection_color.xml | 5 + .../main/res/color/text_selection_color.xml | 5 + .../main/res/layout/player_custom_layout.xml | 33 +-- .../layout/player_select_source_and_subs.xml | 131 ++++++++++ .../main/res/layout/result_episode_large.xml | 2 +- .../res/layout/sort_bottom_single_choice.xml | 26 +- app/src/main/res/values/strings.xml | 3 + 17 files changed, 450 insertions(+), 101 deletions(-) create mode 100644 app/src/main/res/color/check_selection_color.xml create mode 100644 app/src/main/res/color/text_selection_color.xml create mode 100644 app/src/main/res/layout/player_select_source_and_subs.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index accc6353..ca65cba2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -113,6 +113,8 @@ abstract class MainAPI { } } +class ErrorLoadingException(message: String? = null) : Exception(message) + fun parseRating(ratingString: String?): Int? { if (ratingString == null) return null val floatRating = ratingString.toFloatOrNull() ?: return null @@ -149,6 +151,19 @@ fun sortSubs(urls: List): List { } } +/** https://www.imdb.com/title/tt2861424/ -> tt2861424 */ +fun imdbUrlToId(url: String): String { + return url + .removePrefix("https://www.imdb.com/title/") + .removePrefix("https://imdb.com/title/tt2861424/") + .replace("/", "") +} + +fun imdbUrlToIdNullable(url: String?): String? { + if(url == null) return null + return imdbUrlToId(url) +} + enum class ShowStatus { Completed, Ongoing, @@ -301,7 +316,7 @@ data class MovieLoadResponse( override val year: Int?, override val plot: String?, - val imdbUrl: String?, + val imdbId: String?, override val rating: Int? = null, override val tags: ArrayList? = null, override val duration: String? = null, @@ -331,7 +346,7 @@ data class TvSeriesLoadResponse( override val plot: String?, val showStatus: ShowStatus?, - val imdbUrl: String?, + val imdbId: String?, override val rating: Int? = null, override val tags: ArrayList? = null, override val duration: String? = null, diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 529db96c..6b85eddf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -31,10 +31,8 @@ import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos import com.lagradost.cloudstream3.utils.Event -import com.lagradost.cloudstream3.utils.SubtitleHelper.createISO import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_result.* -import kotlin.concurrent.thread const val VLC_PACKAGE = "org.videolan.vlc" const val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result" @@ -58,7 +56,7 @@ class MainActivity : AppCompatActivity() { return appViewModelStore }*/ companion object { - var isInPlayer: Boolean = false + var canEnterPipMode: Boolean = false var canShowPipMode: Boolean = false var isInPIPMode: Boolean = false @@ -67,7 +65,7 @@ class MainActivity : AppCompatActivity() { } private fun enterPIPMode() { - if (!shouldShowPIPMode(isInPlayer) || !canShowPipMode) return + if (!shouldShowPIPMode(canEnterPipMode) || !canShowPipMode) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { try { enterPictureInPictureMode(PictureInPictureParams.Builder().build()) @@ -83,7 +81,7 @@ class MainActivity : AppCompatActivity() { override fun onUserLeaveHint() { super.onUserLeaveHint() - if (isInPlayer && canShowPipMode) { + if (canEnterPipMode && canShowPipMode) { enterPIPMode() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt index af74d813..68d7928f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/MeloMovieProvider.kt @@ -132,7 +132,7 @@ class MeloMovieProvider : MainAPI() { val plot = document.selectFirst("div.col-lg-12 > p").text() if (type == 1) { // MOVIE - val serialize = document.selectFirst("table.accordion__list") + val serialize = document.selectFirst("table.accordion__list") ?: throw ErrorLoadingException("No links found") return MovieLoadResponse( title, url, @@ -142,11 +142,11 @@ class MeloMovieProvider : MainAPI() { poster, year, plot, - imdbUrl + imdbUrlToIdNullable(imdbUrl) ) } else if (type == 2) { val episodes = ArrayList() - val seasons = document.select("div.accordion__card") + val seasons = document.select("div.accordion__card") ?: throw ErrorLoadingException("No episodes found") for (s in seasons) { val season = s.selectFirst("> div.card-header > button > span").text().replace("Season: ", "").toIntOrNull() @@ -154,7 +154,7 @@ class MeloMovieProvider : MainAPI() { for (e in localEpisodes) { val episode = e.selectFirst("> div.card-header > button > span").text().replace("Episode: ", "").toIntOrNull() - val links = e.selectFirst("> div.collapse > div > table.accordion__list") + val links = e.selectFirst("> div.collapse > div > table.accordion__list") ?: continue val data = serializeData(links) episodes.add(TvSeriesEpisode(null, season, episode, data)) } @@ -170,7 +170,7 @@ class MeloMovieProvider : MainAPI() { year, plot, null, - imdbUrl + imdbUrlToIdNullable(imdbUrl) ) } return null diff --git a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt index 4460207b..9378d283 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/movieproviders/TrailersToProvider.kt @@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.movieproviders import com.fasterxml.jackson.module.kotlin.readValue import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.utils.AppUtils.imdbUrlToId import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.SubtitleHelper @@ -255,7 +254,7 @@ class TrailersToProvider : MainAPI() { year, descript, null, - imdbUrl, + imdbUrlToIdNullable(imdbUrl), rating, tags, duration, @@ -283,7 +282,7 @@ class TrailersToProvider : MainAPI() { poster, year, descript, - imdbUrl, + imdbUrlToIdNullable(imdbUrl), rating, tags, duration, diff --git a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index e4a61e95..73c02145 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import com.bumptech.glide.load.HttpException -import com.lagradost.cloudstream3.ui.ErrorLoadingException +import com.lagradost.cloudstream3.ErrorLoadingException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.net.SocketTimeoutException @@ -30,7 +30,8 @@ sealed class Resource { val errorResponse: Any?, //ResponseBody val errorString: String, ) : Resource() - data class Loading(val url : String? = null) : Resource() + + data class Loading(val url: String? = null) : Resource() } fun logError(throwable: Throwable) { @@ -41,7 +42,7 @@ fun logError(throwable: Throwable) { Log.d("ApiError", "-------------------------------------------------------------------") } -fun normalSafeApiCall(apiCall : () -> T) : T? { +fun normalSafeApiCall(apiCall: () -> T): T? { return try { apiCall.invoke() } catch (throwable: Throwable) { @@ -69,7 +70,7 @@ suspend fun safeApiCall( Resource.Failure(true, null, null, "Cannot connect to server, try again later.") } is ErrorLoadingException -> { - Resource.Failure(true, null, null, "Error loading, try again later.") + Resource.Failure(true, null, null, throwable.message ?: "Error loading, try again later.") } else -> { val stackTraceMsg = throwable.localizedMessage + "\n\n" + throwable.stackTrace.joinToString( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt index 62d5b086..533262df 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt @@ -6,8 +6,6 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.utils.ExtractorLink -class ErrorLoadingException(message: String) : Exception(message) - class APIRepository(val api: MainAPI) { val name: String get() = api.name val mainUrl: String get() = api.mainUrl @@ -15,25 +13,25 @@ class APIRepository(val api: MainAPI) { suspend fun load(url: String): Resource { return safeApiCall { // remove suffix for some slugs to handle correctly - api.load(url.removeSuffix("/")) ?: throw ErrorLoadingException("Error Loading") + api.load(url.removeSuffix("/")) ?: throw ErrorLoadingException() } } suspend fun search(query: String): Resource> { return safeApiCall { - api.search(query) ?: throw ErrorLoadingException("Error Loading") + api.search(query) ?: throw ErrorLoadingException() } } suspend fun quickSearch(query: String): Resource> { return safeApiCall { - api.quickSearch(query) ?: throw ErrorLoadingException("Error Loading") + api.quickSearch(query) ?: throw ErrorLoadingException() } } suspend fun getMainPage(): Resource { return safeApiCall { - api.getMainPage() ?: throw ErrorLoadingException("Error Loading") + api.getMainPage() ?: throw ErrorLoadingException() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt index 641a67fa..1d59b2cd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt @@ -23,8 +23,6 @@ import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActi import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SubtitleFile -import com.lagradost.cloudstream3.mvvm.Resource -import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.sortUrls import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.CastHelper.awaitLinks @@ -97,7 +95,6 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi // lateinit var dialog: AlertDialog val holder = getCurrentMetaData() - if (holder != null) { val items = holder.currentLinks if (items.isNotEmpty() && remoteMediaClient?.currentItem != null) { @@ -251,7 +248,6 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi ) VISIBLE else INVISIBLE try { if (meta != null && meta.episodes.size > meta.currentEpisodeIndex + 1) { - val currentIdIndex = remoteMediaClient?.getItemIndex() ?: return val itemCount = remoteMediaClient?.mediaQueue?.itemCount @@ -264,8 +260,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi val links = ArrayList() val subs = ArrayList() - val res = safeApiCall { - getApiFromName(meta.apiName).loadLinks(epData.data, true, { subtitleFile -> + val isSuccessful = + APIRepository(getApiFromName(meta.apiName)).loadLinks(epData.data, true, { subtitleFile -> if (!subs.any { it.url == subtitleFile.url }) { subs.add(subtitleFile) } @@ -274,9 +270,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi links.add(link) } } - } - if (res is Resource.Success) { + if (isSuccessful) { val sorted = sortUrls(links) if (sorted.isNotEmpty()) { val jsonCopy = meta.copy( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt index 2008151e..be3c12c1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt @@ -12,6 +12,7 @@ import android.content.pm.ActivityInfo import android.content.res.Resources import android.database.ContentObserver import android.graphics.Color +import android.graphics.Typeface import android.graphics.drawable.Icon import android.media.AudioManager import android.net.Uri @@ -25,8 +26,7 @@ import android.view.animation.AccelerateInterpolator import android.view.animation.AlphaAnimation import android.view.animation.Animation import android.view.animation.AnimationUtils -import android.widget.ProgressBar -import android.widget.Toast +import android.widget.* import android.widget.Toast.LENGTH_SHORT import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat @@ -45,6 +45,8 @@ import com.google.android.exoplayer2.C.TIME_UNSET import com.google.android.exoplayer2.source.DefaultMediaSourceFactory import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.ui.AspectRatioFrameLayout +import com.google.android.exoplayer2.ui.CaptionStyleCompat +import com.google.android.exoplayer2.ui.SubtitleView import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory @@ -53,17 +55,11 @@ import com.google.android.exoplayer2.util.Util import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastState +import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.MainActivity.Companion.isInPIPMode -import com.lagradost.cloudstream3.MainActivity.Companion.isInPlayer +import com.lagradost.cloudstream3.MainActivity.Companion.canEnterPipMode import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.utils.UIHelper.getNavigationBarHeight -import com.lagradost.cloudstream3.utils.UIHelper.getStatusBarHeight -import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard -import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI -import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage -import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI -import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeDirectly @@ -79,7 +75,13 @@ import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.UIHelper +import com.lagradost.cloudstream3.utils.UIHelper.getNavigationBarHeight +import com.lagradost.cloudstream3.utils.UIHelper.getStatusBarHeight +import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard +import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI +import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage +import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI +import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.VIDEO_PLAYER_BRIGHTNESS import com.lagradost.cloudstream3.utils.getId import kotlinx.android.synthetic.main.fragment_player.* @@ -791,6 +793,14 @@ class PlayerFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val subs = player_view.findViewById(R.id.exo_subtitles) + subs.setStyle( + CaptionStyleCompat( + Color.WHITE, Color.TRANSPARENT, Color.TRANSPARENT, CaptionStyleCompat.EDGE_TYPE_OUTLINE, Color.BLACK, + Typeface.SANS_SERIF + ) + ) + settingsManager = PreferenceManager.getDefaultSharedPreferences(activity) swipeEnabled = settingsManager.getBoolean("swipe_enabled", true) swipeVerticalEnabled = settingsManager.getBoolean("swipe_vertical_enabled", true) @@ -800,8 +810,6 @@ class PlayerFragment : Fragment() { brightness_overlay?.alpha = context?.getKey(VIDEO_PLAYER_BRIGHTNESS, 0f) ?: 0f - isInPlayer = true // NEED REFERENCE TO MAIN ACTIVITY FOR PIP - navigationBarHeight = requireContext().getNavigationBarHeight() statusBarHeight = requireContext().getStatusBarHeight() @@ -898,9 +906,9 @@ class PlayerFragment : Fragment() { } sources_btt.visibility = - if (isDownloadedFile) View.GONE else View.VISIBLE + if (isDownloadedFile) GONE else VISIBLE player_media_route_button.visibility = - if (isDownloadedFile) View.GONE else View.VISIBLE + if (isDownloadedFile) GONE else VISIBLE if (savedInstanceState != null) { currentWindow = savedInstanceState.getInt(STATE_RESUME_WINDOW) playbackPosition = savedInstanceState.getLong(STATE_RESUME_POSITION) @@ -1138,27 +1146,126 @@ class PlayerFragment : Fragment() { lateinit var dialog: AlertDialog getUrls()?.let { it1 -> sortUrls(it1).let { sources -> + val isPlaying = exoPlayer.isPlaying + exoPlayer.pause() + val currentSubtitles = activeSubtitles + + val sourceBuilder = AlertDialog.Builder(view.context, R.style.AlertDialogCustomBlack) + .setView(R.layout.player_select_source_and_subs) + + val sourceDialog = sourceBuilder.create() + sourceDialog.show() + // bottomSheetDialog.setContentView(R.layout.sort_bottom_sheet) + val providerList = sourceDialog.findViewById(R.id.sort_providers)!! + val subtitleList = sourceDialog.findViewById(R.id.sort_subtitles)!! + val applyButton = sourceDialog.findViewById(R.id.pick_source_apply)!! + val cancelButton = sourceDialog.findViewById(R.id.pick_source_cancel)!! + + val startSource = sources.indexOf(getCurrentUrl()) + var sourceIndex = startSource + val startSubtitle = currentSubtitles.indexOf(preferredSubtitles) + 1 + var subtitleIndex = startSubtitle + + if (currentSubtitles.isEmpty()) { + sourceDialog.findViewById(R.id.sort_subtitles_holder)?.visibility = GONE + } else { + val subsArrayAdapter = ArrayAdapter(view.context, R.layout.sort_bottom_single_choice) + subsArrayAdapter.add("No Subtitles") + subsArrayAdapter.addAll(currentSubtitles) + + subtitleList.adapter = subsArrayAdapter + subtitleList.choiceMode = AbsListView.CHOICE_MODE_SINGLE + + subtitleList.setSelection(subtitleIndex) + subtitleList.setItemChecked(subtitleIndex, true) + + subtitleList.setOnItemClickListener { _, _, which, _ -> + subtitleIndex = which + subtitleList.setItemChecked(which, true) + } + } + + val sourcesArrayAdapter = ArrayAdapter(view.context, R.layout.sort_bottom_single_choice) + sourcesArrayAdapter.addAll(sources.map { it.name }) + + providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE + providerList.adapter = sourcesArrayAdapter + providerList.setSelection(sourceIndex) + providerList.setItemChecked(sourceIndex, true) + + providerList.setOnItemClickListener { _, _, which, _ -> + sourceIndex = which + providerList.setItemChecked(which, true) + } + + sourceDialog.setOnDismissListener { + activity?.hideSystemUI() + } + + cancelButton.setOnClickListener { + sourceDialog.dismiss() + } + + applyButton.setOnClickListener { + if (sourceIndex != startSource) { + playbackPosition = if (this::exoPlayer.isInitialized) exoPlayer.currentPosition else 0 + setMirrorId(sources[sourceIndex].getId()) + initPlayer(getCurrentUrl()) + } else { + if (isPlaying) { + // exoPlayer.play() + } + } + + if (subtitleIndex != startSubtitle) { + val textRendererIndex = getRendererIndex(C.TRACK_TYPE_TEXT) ?: return@setOnClickListener + (exoPlayer.trackSelector as DefaultTrackSelector?)?.let { trackSelector -> + if (subtitleIndex <= 0) { + preferredSubtitles = "" + trackSelector.setParameters( + trackSelector.buildUponParameters() + .setPreferredTextLanguage("") + .setRendererDisabled(textRendererIndex, true) + ) + } else { + val currentPreferredSub = currentSubtitles[subtitleIndex - 1] + preferredSubtitles = currentPreferredSub + trackSelector.setParameters( + trackSelector.buildUponParameters() + .setPreferredTextLanguage(currentPreferredSub) + .setRendererDisabled(textRendererIndex, false) + ) + } + } + } + sourceDialog.dismiss() + } + /* + + */ + /* + val sourcesText = sources.map { it.name } val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) - builder.setTitle("Pick source") - builder.setOnDismissListener { - activity?.hideSystemUI() - } - builder.setSingleChoiceItems( - sourcesText.toTypedArray(), - sources.indexOf(getCurrentUrl()) - ) { _, which -> - //val speed = speedsText[which] - //Toast.makeText(requireContext(), "$speed selected.", Toast.LENGTH_SHORT).show() - playbackPosition = if (this::exoPlayer.isInitialized) exoPlayer.currentPosition else 0 - setMirrorId(sources[which].getId()) - initPlayer(getCurrentUrl()) + builder.setTitle("Pick source") + builder.setOnDismissListener { + activity?.hideSystemUI() + } + builder.setSingleChoiceItems( + sourcesText.toTypedArray(), + sources.indexOf(getCurrentUrl()) + ) { _, which -> + //val speed = speedsText[which] + //Toast.makeText(requireContext(), "$speed selected.", Toast.LENGTH_SHORT).show() + playbackPosition = if (this::exoPlayer.isInitialized) exoPlayer.currentPosition else 0 + setMirrorId(sources[which].getId()) + initPlayer(getCurrentUrl()) - dialog.dismiss() - activity?.hideSystemUI() - } - dialog = builder.create() - dialog.show() + dialog.dismiss() + activity?.hideSystemUI() + } + dialog = builder.create() + dialog.show()*/ } } } @@ -1192,6 +1299,18 @@ class PlayerFragment : Fragment() { // initPlayer() } + private fun getRendererIndex(trackIndex: Int): Int? { + if (!this::exoPlayer.isInitialized) return null + + for (renderIndex in 0 until exoPlayer.rendererCount) { + if (exoPlayer.getRendererType(renderIndex) == renderIndex) { + return renderIndex + } + } + + return null + } + private fun getCurrentUrl(): ExtractorLink? { val urls = getUrls() ?: return null for (i in urls) { @@ -1201,12 +1320,6 @@ class PlayerFragment : Fragment() { } return null - /*ExtractorLink("", - "TEST", - "https://v6.4animu.me/Overlord/Overlord-Episode-01-1080p.mp4", - //"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", - "", - 0)*/ } private fun getUrls(): List? { @@ -1306,7 +1419,7 @@ class PlayerFragment : Fragment() { savePos() super.onDestroy() - isInPlayer = false + canEnterPipMode = false savePositionInPlayer() safeReleasePlayer() @@ -1377,6 +1490,18 @@ class PlayerFragment : Fragment() { private val updateProgressAction = Runnable { updateProgressBar() }*/ + private fun String.toSubtitleMimeType(): String { + return when { + endsWith("vtt", true) -> MimeTypes.TEXT_VTT + endsWith("srt", true) -> MimeTypes.APPLICATION_SUBRIP + endsWith("xml", true) || endsWith("ttml", true) -> MimeTypes.APPLICATION_TTML + else -> MimeTypes.TEXT_VTT + } + } + + var activeSubtitles: List = listOf() + var preferredSubtitles: String = "" + @SuppressLint("SetTextI18n") fun initPlayer(currentUrl: ExtractorLink?, uri: String? = null) { if (currentUrl == null && uri == null) return @@ -1384,7 +1509,6 @@ class PlayerFragment : Fragment() { hasUsedFirstRender = false try { - if (!isInPlayer) return if (this::exoPlayer.isInitialized) { savePos() exoPlayer.release() @@ -1446,11 +1570,35 @@ class PlayerFragment : Fragment() { } } + val subs = getSubs() + if (subs != null) { + val subItems = ArrayList() + val subItemsId = ArrayList() + + for (sub in sortSubs(subs)) { + val langId = sub.lang //SubtitleHelper.fromLanguageToTwoLetters(it.lang) ?: it.lang + subItemsId.add(langId) + subItems.add( + MediaItem.Subtitle( + Uri.parse(sub.url), + sub.url.toSubtitleMimeType(), + langId, + C.SELECTION_FLAG_DEFAULT + ) + ) + } + + activeSubtitles = subItemsId + mediaItemBuilder.setSubtitles(subItems) + } + +//might add https://github.com/ed828a/Aihua/blob/1896f46888b5a954b367e83f40b845ce174a2328/app/src/main/java/com/dew/aihua/player/playerUI/VideoPlayer.kt#L287 toggle caps + val mediaItem = mediaItemBuilder.build() val trackSelector = DefaultTrackSelector(requireContext()) // Disable subtitles trackSelector.parameters = DefaultTrackSelector.ParametersBuilder(requireContext()) - .setRendererDisabled(C.TRACK_TYPE_VIDEO, true) + // .setRendererDisabled(C.TRACK_TYPE_VIDEO, true) .setRendererDisabled(C.TRACK_TYPE_TEXT, true) .setDisabledTextTrackSelectionFlags(C.TRACK_TYPE_TEXT) .clearSelectionOverrides() @@ -1530,6 +1678,18 @@ class PlayerFragment : Fragment() { */ + /*exoPlayer.addTextOutput { list -> + if (list.size == 0) return@addTextOutput + + val textBuilder = StringBuilder() + for (cue in list) { + textBuilder.append(cue.text).append("\n") + } + val subtitleText = if (textBuilder.isNotEmpty()) + textBuilder.substring(0, textBuilder.length - 1) + else + textBuilder.toString() + }*/ //https://stackoverflow.com/questions/47731779/detect-pause-resume-in-exoplayer exoPlayer.addListener(object : Player.Listener { @@ -1573,6 +1733,7 @@ class PlayerFragment : Fragment() { } override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { + canEnterPipMode = exoPlayer.isPlaying updatePIPModeActions() if (activity == null) return if (playWhenReady) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 84b5c6cf..9f4888e8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -1,11 +1,13 @@ package com.lagradost.cloudstream3.ui.result import android.annotation.SuppressLint +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import android.widget.Toast import androidx.annotation.LayoutRes import androidx.core.widget.ContentLoadingProgressBar import androidx.recyclerview.widget.RecyclerView @@ -174,6 +176,15 @@ class EpisodeAdapter( episodeDescript?.visibility = View.GONE } + episodePoster?.setOnClickListener { + clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) + } + + episodePoster?.setOnLongClickListener { + Toast.makeText(it.context, R.string.play_episode_toast, Toast.LENGTH_SHORT).show() + return@setOnLongClickListener true + } + episodeHolder.setOnClickListener { clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) } @@ -197,7 +208,15 @@ class EpisodeAdapter( downloadButton.setUpButton( downloadInfo?.fileLength, downloadInfo?.totalBytes, episodeDownloadBar, episodeDownloadImage, null, VideoDownloadHelper.DownloadEpisodeCached( - card.name, card.poster, card.episode, card.season, card.id, 0, card.rating, card.descript, System.currentTimeMillis(), + card.name, + card.poster, + card.episode, + card.season, + card.id, + 0, + card.rating, + card.descript, + System.currentTimeMillis(), ) ) { if (it.action == DOWNLOAD_ACTION_DOWNLOAD) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index bcf7a975..f6ae40e0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -155,12 +155,4 @@ object AppUtils { } return currentAudioFocusRequest } - - /** https://www.imdb.com/title/tt2861424/ -> tt2861424 */ - fun imdbUrlToId(url: String): String { - return url - .removePrefix("https://www.imdb.com/title/") - .removePrefix("https://imdb.com/title/tt2861424/") - .replace("/", "") - } } \ No newline at end of file diff --git a/app/src/main/res/color/check_selection_color.xml b/app/src/main/res/color/check_selection_color.xml new file mode 100644 index 00000000..1f15490b --- /dev/null +++ b/app/src/main/res/color/check_selection_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/text_selection_color.xml b/app/src/main/res/color/text_selection_color.xml new file mode 100644 index 00000000..c7b17182 --- /dev/null +++ b/app/src/main/res/color/text_selection_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index 1501e810..ca805abf 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -1,17 +1,19 @@ - - + - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/result_episode_large.xml b/app/src/main/res/layout/result_episode_large.xml index db0ca4da..0f39fcee 100644 --- a/app/src/main/res/layout/result_episode_large.xml +++ b/app/src/main/res/layout/result_episode_large.xml @@ -22,11 +22,11 @@ android:layout_height="wrap_content"> +--> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 98ab7813..260b2173 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,4 +66,7 @@ Filter Bookmarks Bookmarks Remove + Play Episode + Apply + Cancel \ No newline at end of file