forked from recloudstream/cloudstream
		
	subs
This commit is contained in:
		
							parent
							
								
									6b27db036b
								
							
						
					
					
						commit
						3f8229756d
					
				
					 17 changed files with 450 additions and 101 deletions
				
			
		|  | @ -113,6 +113,8 @@ abstract class MainAPI { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | class ErrorLoadingException(message: String? = null) : Exception(message) | ||||||
|  | 
 | ||||||
| fun parseRating(ratingString: String?): Int? { | fun parseRating(ratingString: String?): Int? { | ||||||
|     if (ratingString == null) return null |     if (ratingString == null) return null | ||||||
|     val floatRating = ratingString.toFloatOrNull() ?: return null |     val floatRating = ratingString.toFloatOrNull() ?: return null | ||||||
|  | @ -149,6 +151,19 @@ fun sortSubs(urls: List<SubtitleFile>): List<SubtitleFile> { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** 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 { | enum class ShowStatus { | ||||||
|     Completed, |     Completed, | ||||||
|     Ongoing, |     Ongoing, | ||||||
|  | @ -301,7 +316,7 @@ data class MovieLoadResponse( | ||||||
|     override val year: Int?, |     override val year: Int?, | ||||||
|     override val plot: String?, |     override val plot: String?, | ||||||
| 
 | 
 | ||||||
|     val imdbUrl: String?, |     val imdbId: String?, | ||||||
|     override val rating: Int? = null, |     override val rating: Int? = null, | ||||||
|     override val tags: ArrayList<String>? = null, |     override val tags: ArrayList<String>? = null, | ||||||
|     override val duration: String? = null, |     override val duration: String? = null, | ||||||
|  | @ -331,7 +346,7 @@ data class TvSeriesLoadResponse( | ||||||
|     override val plot: String?, |     override val plot: String?, | ||||||
| 
 | 
 | ||||||
|     val showStatus: ShowStatus?, |     val showStatus: ShowStatus?, | ||||||
|     val imdbUrl: String?, |     val imdbId: String?, | ||||||
|     override val rating: Int? = null, |     override val rating: Int? = null, | ||||||
|     override val tags: ArrayList<String>? = null, |     override val tags: ArrayList<String>? = null, | ||||||
|     override val duration: String? = null, |     override val duration: String? = null, | ||||||
|  |  | ||||||
|  | @ -31,10 +31,8 @@ import com.lagradost.cloudstream3.utils.DataStore.getKey | ||||||
| import com.lagradost.cloudstream3.utils.DataStore.removeKey | import com.lagradost.cloudstream3.utils.DataStore.removeKey | ||||||
| import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos | import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos | ||||||
| import com.lagradost.cloudstream3.utils.Event | 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.activity_main.* | ||||||
| import kotlinx.android.synthetic.main.fragment_result.* | import kotlinx.android.synthetic.main.fragment_result.* | ||||||
| import kotlin.concurrent.thread |  | ||||||
| 
 | 
 | ||||||
| const val VLC_PACKAGE = "org.videolan.vlc" | const val VLC_PACKAGE = "org.videolan.vlc" | ||||||
| const val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result" | const val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result" | ||||||
|  | @ -58,7 +56,7 @@ class MainActivity : AppCompatActivity() { | ||||||
|             return appViewModelStore |             return appViewModelStore | ||||||
|         }*/ |         }*/ | ||||||
|     companion object { |     companion object { | ||||||
|         var isInPlayer: Boolean = false |         var canEnterPipMode: Boolean = false | ||||||
|         var canShowPipMode: Boolean = false |         var canShowPipMode: Boolean = false | ||||||
|         var isInPIPMode: Boolean = false |         var isInPIPMode: Boolean = false | ||||||
| 
 | 
 | ||||||
|  | @ -67,7 +65,7 @@ class MainActivity : AppCompatActivity() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun enterPIPMode() { |     private fun enterPIPMode() { | ||||||
|         if (!shouldShowPIPMode(isInPlayer) || !canShowPipMode) return |         if (!shouldShowPIPMode(canEnterPipMode) || !canShowPipMode) return | ||||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||||
|             try { |             try { | ||||||
|                 enterPictureInPictureMode(PictureInPictureParams.Builder().build()) |                 enterPictureInPictureMode(PictureInPictureParams.Builder().build()) | ||||||
|  | @ -83,7 +81,7 @@ class MainActivity : AppCompatActivity() { | ||||||
| 
 | 
 | ||||||
|     override fun onUserLeaveHint() { |     override fun onUserLeaveHint() { | ||||||
|         super.onUserLeaveHint() |         super.onUserLeaveHint() | ||||||
|         if (isInPlayer && canShowPipMode) { |         if (canEnterPipMode && canShowPipMode) { | ||||||
|             enterPIPMode() |             enterPIPMode() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -132,7 +132,7 @@ class MeloMovieProvider : MainAPI() { | ||||||
|         val plot = document.selectFirst("div.col-lg-12 > p").text() |         val plot = document.selectFirst("div.col-lg-12 > p").text() | ||||||
| 
 | 
 | ||||||
|         if (type == 1) { // MOVIE |         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( |             return MovieLoadResponse( | ||||||
|                 title, |                 title, | ||||||
|                 url, |                 url, | ||||||
|  | @ -142,11 +142,11 @@ class MeloMovieProvider : MainAPI() { | ||||||
|                 poster, |                 poster, | ||||||
|                 year, |                 year, | ||||||
|                 plot, |                 plot, | ||||||
|                 imdbUrl |                 imdbUrlToIdNullable(imdbUrl) | ||||||
|             ) |             ) | ||||||
|         } else if (type == 2) { |         } else if (type == 2) { | ||||||
|             val episodes = ArrayList<TvSeriesEpisode>() |             val episodes = ArrayList<TvSeriesEpisode>() | ||||||
|             val seasons = document.select("div.accordion__card") |             val seasons = document.select("div.accordion__card") ?: throw ErrorLoadingException("No episodes found") | ||||||
|             for (s in seasons) { |             for (s in seasons) { | ||||||
|                 val season = |                 val season = | ||||||
|                     s.selectFirst("> div.card-header > button > span").text().replace("Season: ", "").toIntOrNull() |                     s.selectFirst("> div.card-header > button > span").text().replace("Season: ", "").toIntOrNull() | ||||||
|  | @ -154,7 +154,7 @@ class MeloMovieProvider : MainAPI() { | ||||||
|                 for (e in localEpisodes) { |                 for (e in localEpisodes) { | ||||||
|                     val episode = |                     val episode = | ||||||
|                         e.selectFirst("> div.card-header > button > span").text().replace("Episode: ", "").toIntOrNull() |                         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) |                     val data = serializeData(links) | ||||||
|                     episodes.add(TvSeriesEpisode(null, season, episode, data)) |                     episodes.add(TvSeriesEpisode(null, season, episode, data)) | ||||||
|                 } |                 } | ||||||
|  | @ -170,7 +170,7 @@ class MeloMovieProvider : MainAPI() { | ||||||
|                 year, |                 year, | ||||||
|                 plot, |                 plot, | ||||||
|                 null, |                 null, | ||||||
|                 imdbUrl |                 imdbUrlToIdNullable(imdbUrl) | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|         return null |         return null | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ package com.lagradost.cloudstream3.movieproviders | ||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.module.kotlin.readValue | import com.fasterxml.jackson.module.kotlin.readValue | ||||||
| import com.lagradost.cloudstream3.* | import com.lagradost.cloudstream3.* | ||||||
| import com.lagradost.cloudstream3.utils.AppUtils.imdbUrlToId |  | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import com.lagradost.cloudstream3.utils.Qualities | import com.lagradost.cloudstream3.utils.Qualities | ||||||
| import com.lagradost.cloudstream3.utils.SubtitleHelper | import com.lagradost.cloudstream3.utils.SubtitleHelper | ||||||
|  | @ -255,7 +254,7 @@ class TrailersToProvider : MainAPI() { | ||||||
|                 year, |                 year, | ||||||
|                 descript, |                 descript, | ||||||
|                 null, |                 null, | ||||||
|                 imdbUrl, |                 imdbUrlToIdNullable(imdbUrl), | ||||||
|                 rating, |                 rating, | ||||||
|                 tags, |                 tags, | ||||||
|                 duration, |                 duration, | ||||||
|  | @ -283,7 +282,7 @@ class TrailersToProvider : MainAPI() { | ||||||
|                 poster, |                 poster, | ||||||
|                 year, |                 year, | ||||||
|                 descript, |                 descript, | ||||||
|                 imdbUrl, |                 imdbUrlToIdNullable(imdbUrl), | ||||||
|                 rating, |                 rating, | ||||||
|                 tags, |                 tags, | ||||||
|                 duration, |                 duration, | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import androidx.lifecycle.LifecycleOwner | ||||||
| import androidx.lifecycle.LiveData | import androidx.lifecycle.LiveData | ||||||
| import androidx.lifecycle.Observer | import androidx.lifecycle.Observer | ||||||
| import com.bumptech.glide.load.HttpException | import com.bumptech.glide.load.HttpException | ||||||
| import com.lagradost.cloudstream3.ui.ErrorLoadingException | import com.lagradost.cloudstream3.ErrorLoadingException | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.withContext | import kotlinx.coroutines.withContext | ||||||
| import java.net.SocketTimeoutException | import java.net.SocketTimeoutException | ||||||
|  | @ -30,7 +30,8 @@ sealed class Resource<out T> { | ||||||
|         val errorResponse: Any?, //ResponseBody |         val errorResponse: Any?, //ResponseBody | ||||||
|         val errorString: String, |         val errorString: String, | ||||||
|     ) : Resource<Nothing>() |     ) : Resource<Nothing>() | ||||||
|     data class Loading(val url : String? = null)  : Resource<Nothing>() | 
 | ||||||
|  |     data class Loading(val url: String? = null) : Resource<Nothing>() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fun logError(throwable: Throwable) { | fun logError(throwable: Throwable) { | ||||||
|  | @ -41,7 +42,7 @@ fun logError(throwable: Throwable) { | ||||||
|     Log.d("ApiError", "-------------------------------------------------------------------") |     Log.d("ApiError", "-------------------------------------------------------------------") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fun<T> normalSafeApiCall(apiCall : () -> T) : T? { | fun <T> normalSafeApiCall(apiCall: () -> T): T? { | ||||||
|     return try { |     return try { | ||||||
|         apiCall.invoke() |         apiCall.invoke() | ||||||
|     } catch (throwable: Throwable) { |     } catch (throwable: Throwable) { | ||||||
|  | @ -69,7 +70,7 @@ suspend fun <T> safeApiCall( | ||||||
|                     Resource.Failure(true, null, null, "Cannot connect to server, try again later.") |                     Resource.Failure(true, null, null, "Cannot connect to server, try again later.") | ||||||
|                 } |                 } | ||||||
|                 is ErrorLoadingException -> { |                 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 -> { |                 else -> { | ||||||
|                     val stackTraceMsg = throwable.localizedMessage + "\n\n" + throwable.stackTrace.joinToString( |                     val stackTraceMsg = throwable.localizedMessage + "\n\n" + throwable.stackTrace.joinToString( | ||||||
|  |  | ||||||
|  | @ -6,8 +6,6 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall | ||||||
| import com.lagradost.cloudstream3.mvvm.safeApiCall | import com.lagradost.cloudstream3.mvvm.safeApiCall | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| 
 | 
 | ||||||
| class ErrorLoadingException(message: String) : Exception(message) |  | ||||||
| 
 |  | ||||||
| class APIRepository(val api: MainAPI) { | class APIRepository(val api: MainAPI) { | ||||||
|     val name: String get() = api.name |     val name: String get() = api.name | ||||||
|     val mainUrl: String get() = api.mainUrl |     val mainUrl: String get() = api.mainUrl | ||||||
|  | @ -15,25 +13,25 @@ class APIRepository(val api: MainAPI) { | ||||||
|     suspend fun load(url: String): Resource<LoadResponse> { |     suspend fun load(url: String): Resource<LoadResponse> { | ||||||
|         return safeApiCall { |         return safeApiCall { | ||||||
|             // remove suffix for some slugs to handle correctly |             // 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<ArrayList<SearchResponse>> { |     suspend fun search(query: String): Resource<ArrayList<SearchResponse>> { | ||||||
|         return safeApiCall { |         return safeApiCall { | ||||||
|             api.search(query) ?: throw ErrorLoadingException("Error Loading") |             api.search(query) ?: throw ErrorLoadingException() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     suspend fun quickSearch(query: String): Resource<ArrayList<SearchResponse>> { |     suspend fun quickSearch(query: String): Resource<ArrayList<SearchResponse>> { | ||||||
|         return safeApiCall { |         return safeApiCall { | ||||||
|             api.quickSearch(query) ?: throw ErrorLoadingException("Error Loading") |             api.quickSearch(query) ?: throw ErrorLoadingException() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     suspend fun getMainPage(): Resource<HomePageResponse> { |     suspend fun getMainPage(): Resource<HomePageResponse> { | ||||||
|         return safeApiCall { |         return safeApiCall { | ||||||
|             api.getMainPage() ?: throw ErrorLoadingException("Error Loading") |             api.getMainPage() ?: throw ErrorLoadingException() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -23,8 +23,6 @@ import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActi | ||||||
| import com.lagradost.cloudstream3.APIHolder.getApiFromName | import com.lagradost.cloudstream3.APIHolder.getApiFromName | ||||||
| import com.lagradost.cloudstream3.R | import com.lagradost.cloudstream3.R | ||||||
| import com.lagradost.cloudstream3.SubtitleFile | 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.sortUrls | ||||||
| import com.lagradost.cloudstream3.ui.result.ResultEpisode | import com.lagradost.cloudstream3.ui.result.ResultEpisode | ||||||
| import com.lagradost.cloudstream3.utils.CastHelper.awaitLinks | import com.lagradost.cloudstream3.utils.CastHelper.awaitLinks | ||||||
|  | @ -97,7 +95,6 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi | ||||||
|             //  lateinit var dialog: AlertDialog |             //  lateinit var dialog: AlertDialog | ||||||
|             val holder = getCurrentMetaData() |             val holder = getCurrentMetaData() | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|             if (holder != null) { |             if (holder != null) { | ||||||
|                 val items = holder.currentLinks |                 val items = holder.currentLinks | ||||||
|                 if (items.isNotEmpty() && remoteMediaClient?.currentItem != null) { |                 if (items.isNotEmpty() && remoteMediaClient?.currentItem != null) { | ||||||
|  | @ -251,7 +248,6 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi | ||||||
|         ) VISIBLE else INVISIBLE |         ) VISIBLE else INVISIBLE | ||||||
|         try { |         try { | ||||||
|             if (meta != null && meta.episodes.size > meta.currentEpisodeIndex + 1) { |             if (meta != null && meta.episodes.size > meta.currentEpisodeIndex + 1) { | ||||||
| 
 |  | ||||||
|                 val currentIdIndex = remoteMediaClient?.getItemIndex() ?: return |                 val currentIdIndex = remoteMediaClient?.getItemIndex() ?: return | ||||||
|                 val itemCount = remoteMediaClient?.mediaQueue?.itemCount |                 val itemCount = remoteMediaClient?.mediaQueue?.itemCount | ||||||
| 
 | 
 | ||||||
|  | @ -264,8 +260,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi | ||||||
|                         val links = ArrayList<ExtractorLink>() |                         val links = ArrayList<ExtractorLink>() | ||||||
|                         val subs = ArrayList<SubtitleFile>() |                         val subs = ArrayList<SubtitleFile>() | ||||||
| 
 | 
 | ||||||
|                         val res = safeApiCall { |                         val isSuccessful = | ||||||
|                             getApiFromName(meta.apiName).loadLinks(epData.data, true, { subtitleFile -> |                             APIRepository(getApiFromName(meta.apiName)).loadLinks(epData.data, true, { subtitleFile -> | ||||||
|                                 if (!subs.any { it.url == subtitleFile.url }) { |                                 if (!subs.any { it.url == subtitleFile.url }) { | ||||||
|                                     subs.add(subtitleFile) |                                     subs.add(subtitleFile) | ||||||
|                                 } |                                 } | ||||||
|  | @ -274,9 +270,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi | ||||||
|                                     links.add(link) |                                     links.add(link) | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                         } |  | ||||||
| 
 | 
 | ||||||
|                         if (res is Resource.Success) { |                         if (isSuccessful) { | ||||||
|                             val sorted = sortUrls(links) |                             val sorted = sortUrls(links) | ||||||
|                             if (sorted.isNotEmpty()) { |                             if (sorted.isNotEmpty()) { | ||||||
|                                 val jsonCopy = meta.copy( |                                 val jsonCopy = meta.copy( | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ import android.content.pm.ActivityInfo | ||||||
| import android.content.res.Resources | import android.content.res.Resources | ||||||
| import android.database.ContentObserver | import android.database.ContentObserver | ||||||
| import android.graphics.Color | import android.graphics.Color | ||||||
|  | import android.graphics.Typeface | ||||||
| import android.graphics.drawable.Icon | import android.graphics.drawable.Icon | ||||||
| import android.media.AudioManager | import android.media.AudioManager | ||||||
| import android.net.Uri | import android.net.Uri | ||||||
|  | @ -25,8 +26,7 @@ import android.view.animation.AccelerateInterpolator | ||||||
| import android.view.animation.AlphaAnimation | import android.view.animation.AlphaAnimation | ||||||
| import android.view.animation.Animation | import android.view.animation.Animation | ||||||
| import android.view.animation.AnimationUtils | import android.view.animation.AnimationUtils | ||||||
| import android.widget.ProgressBar | import android.widget.* | ||||||
| import android.widget.Toast |  | ||||||
| import android.widget.Toast.LENGTH_SHORT | import android.widget.Toast.LENGTH_SHORT | ||||||
| import androidx.appcompat.app.AlertDialog | import androidx.appcompat.app.AlertDialog | ||||||
| import androidx.core.content.ContextCompat | 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.source.DefaultMediaSourceFactory | ||||||
| import com.google.android.exoplayer2.trackselection.DefaultTrackSelector | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector | ||||||
| import com.google.android.exoplayer2.ui.AspectRatioFrameLayout | 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.DataSource | ||||||
| import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory | ||||||
| import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory | 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.CastButtonFactory | ||||||
| import com.google.android.gms.cast.framework.CastContext | import com.google.android.gms.cast.framework.CastContext | ||||||
| import com.google.android.gms.cast.framework.CastState | import com.google.android.gms.cast.framework.CastState | ||||||
|  | import com.google.android.material.button.MaterialButton | ||||||
| import com.lagradost.cloudstream3.* | import com.lagradost.cloudstream3.* | ||||||
| import com.lagradost.cloudstream3.MainActivity.Companion.isInPIPMode | 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.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.Resource | ||||||
| import com.lagradost.cloudstream3.mvvm.observe | import com.lagradost.cloudstream3.mvvm.observe | ||||||
| import com.lagradost.cloudstream3.mvvm.observeDirectly | 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.DataStore.setKey | ||||||
| import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos | import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | 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.VIDEO_PLAYER_BRIGHTNESS | ||||||
| import com.lagradost.cloudstream3.utils.getId | import com.lagradost.cloudstream3.utils.getId | ||||||
| import kotlinx.android.synthetic.main.fragment_player.* | import kotlinx.android.synthetic.main.fragment_player.* | ||||||
|  | @ -791,6 +793,14 @@ class PlayerFragment : Fragment() { | ||||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
| 
 | 
 | ||||||
|  |         val subs = player_view.findViewById<SubtitleView>(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) |         settingsManager = PreferenceManager.getDefaultSharedPreferences(activity) | ||||||
|         swipeEnabled = settingsManager.getBoolean("swipe_enabled", true) |         swipeEnabled = settingsManager.getBoolean("swipe_enabled", true) | ||||||
|         swipeVerticalEnabled = settingsManager.getBoolean("swipe_vertical_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 |         brightness_overlay?.alpha = context?.getKey(VIDEO_PLAYER_BRIGHTNESS, 0f) ?: 0f | ||||||
| 
 | 
 | ||||||
|         isInPlayer = true // NEED REFERENCE TO MAIN ACTIVITY FOR PIP |  | ||||||
| 
 |  | ||||||
|         navigationBarHeight = requireContext().getNavigationBarHeight() |         navigationBarHeight = requireContext().getNavigationBarHeight() | ||||||
|         statusBarHeight = requireContext().getStatusBarHeight() |         statusBarHeight = requireContext().getStatusBarHeight() | ||||||
| 
 | 
 | ||||||
|  | @ -898,9 +906,9 @@ class PlayerFragment : Fragment() { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         sources_btt.visibility = |         sources_btt.visibility = | ||||||
|             if (isDownloadedFile) View.GONE else View.VISIBLE |             if (isDownloadedFile) GONE else VISIBLE | ||||||
|         player_media_route_button.visibility = |         player_media_route_button.visibility = | ||||||
|             if (isDownloadedFile) View.GONE else View.VISIBLE |             if (isDownloadedFile) GONE else VISIBLE | ||||||
|         if (savedInstanceState != null) { |         if (savedInstanceState != null) { | ||||||
|             currentWindow = savedInstanceState.getInt(STATE_RESUME_WINDOW) |             currentWindow = savedInstanceState.getInt(STATE_RESUME_WINDOW) | ||||||
|             playbackPosition = savedInstanceState.getLong(STATE_RESUME_POSITION) |             playbackPosition = savedInstanceState.getLong(STATE_RESUME_POSITION) | ||||||
|  | @ -1138,6 +1146,105 @@ class PlayerFragment : Fragment() { | ||||||
|             lateinit var dialog: AlertDialog |             lateinit var dialog: AlertDialog | ||||||
|             getUrls()?.let { it1 -> |             getUrls()?.let { it1 -> | ||||||
|                 sortUrls(it1).let { sources -> |                 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<ListView>(R.id.sort_providers)!! | ||||||
|  |                     val subtitleList = sourceDialog.findViewById<ListView>(R.id.sort_subtitles)!! | ||||||
|  |                     val applyButton = sourceDialog.findViewById<MaterialButton>(R.id.pick_source_apply)!! | ||||||
|  |                     val cancelButton = sourceDialog.findViewById<MaterialButton>(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<LinearLayout>(R.id.sort_subtitles_holder)?.visibility = GONE | ||||||
|  |                     } else { | ||||||
|  |                         val subsArrayAdapter = ArrayAdapter<String>(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<String>(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 sourcesText = sources.map { it.name } | ||||||
|                     val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) |                     val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) | ||||||
|                         builder.setTitle("Pick source") |                         builder.setTitle("Pick source") | ||||||
|  | @ -1158,7 +1265,7 @@ class PlayerFragment : Fragment() { | ||||||
|                             activity?.hideSystemUI() |                             activity?.hideSystemUI() | ||||||
|                         } |                         } | ||||||
|                         dialog = builder.create() |                         dialog = builder.create() | ||||||
|                     dialog.show() |                         dialog.show()*/ | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -1192,6 +1299,18 @@ class PlayerFragment : Fragment() { | ||||||
|         // initPlayer() |         // 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? { |     private fun getCurrentUrl(): ExtractorLink? { | ||||||
|         val urls = getUrls() ?: return null |         val urls = getUrls() ?: return null | ||||||
|         for (i in urls) { |         for (i in urls) { | ||||||
|  | @ -1201,12 +1320,6 @@ class PlayerFragment : Fragment() { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return null |         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<ExtractorLink>? { |     private fun getUrls(): List<ExtractorLink>? { | ||||||
|  | @ -1306,7 +1419,7 @@ class PlayerFragment : Fragment() { | ||||||
|         savePos() |         savePos() | ||||||
| 
 | 
 | ||||||
|         super.onDestroy() |         super.onDestroy() | ||||||
|         isInPlayer = false |         canEnterPipMode = false | ||||||
| 
 | 
 | ||||||
|         savePositionInPlayer() |         savePositionInPlayer() | ||||||
|         safeReleasePlayer() |         safeReleasePlayer() | ||||||
|  | @ -1377,6 +1490,18 @@ class PlayerFragment : Fragment() { | ||||||
| 
 | 
 | ||||||
|     private val updateProgressAction = Runnable { updateProgressBar() }*/ |     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<String> = listOf() | ||||||
|  |     var preferredSubtitles: String = "" | ||||||
|  | 
 | ||||||
|     @SuppressLint("SetTextI18n") |     @SuppressLint("SetTextI18n") | ||||||
|     fun initPlayer(currentUrl: ExtractorLink?, uri: String? = null) { |     fun initPlayer(currentUrl: ExtractorLink?, uri: String? = null) { | ||||||
|         if (currentUrl == null && uri == null) return |         if (currentUrl == null && uri == null) return | ||||||
|  | @ -1384,7 +1509,6 @@ class PlayerFragment : Fragment() { | ||||||
|         hasUsedFirstRender = false |         hasUsedFirstRender = false | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             if (!isInPlayer) return |  | ||||||
|             if (this::exoPlayer.isInitialized) { |             if (this::exoPlayer.isInitialized) { | ||||||
|                 savePos() |                 savePos() | ||||||
|                 exoPlayer.release() |                 exoPlayer.release() | ||||||
|  | @ -1446,11 +1570,35 @@ class PlayerFragment : Fragment() { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             val subs = getSubs() | ||||||
|  |             if (subs != null) { | ||||||
|  |                 val subItems = ArrayList<MediaItem.Subtitle>() | ||||||
|  |                 val subItemsId = ArrayList<String>() | ||||||
|  | 
 | ||||||
|  |                 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 mediaItem = mediaItemBuilder.build() | ||||||
|             val trackSelector = DefaultTrackSelector(requireContext()) |             val trackSelector = DefaultTrackSelector(requireContext()) | ||||||
|             // Disable subtitles |             // Disable subtitles | ||||||
|             trackSelector.parameters = DefaultTrackSelector.ParametersBuilder(requireContext()) |             trackSelector.parameters = DefaultTrackSelector.ParametersBuilder(requireContext()) | ||||||
|                 .setRendererDisabled(C.TRACK_TYPE_VIDEO, true) |                 // .setRendererDisabled(C.TRACK_TYPE_VIDEO, true) | ||||||
|                 .setRendererDisabled(C.TRACK_TYPE_TEXT, true) |                 .setRendererDisabled(C.TRACK_TYPE_TEXT, true) | ||||||
|                 .setDisabledTextTrackSelectionFlags(C.TRACK_TYPE_TEXT) |                 .setDisabledTextTrackSelectionFlags(C.TRACK_TYPE_TEXT) | ||||||
|                 .clearSelectionOverrides() |                 .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 |             //https://stackoverflow.com/questions/47731779/detect-pause-resume-in-exoplayer | ||||||
|             exoPlayer.addListener(object : Player.Listener { |             exoPlayer.addListener(object : Player.Listener { | ||||||
|  | @ -1573,6 +1733,7 @@ class PlayerFragment : Fragment() { | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { |                 override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { | ||||||
|  |                     canEnterPipMode = exoPlayer.isPlaying | ||||||
|                     updatePIPModeActions() |                     updatePIPModeActions() | ||||||
|                     if (activity == null) return |                     if (activity == null) return | ||||||
|                     if (playWhenReady) { |                     if (playWhenReady) { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,13 @@ | ||||||
| package com.lagradost.cloudstream3.ui.result | package com.lagradost.cloudstream3.ui.result | ||||||
| 
 | 
 | ||||||
| import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||||
|  | import android.view.Gravity | ||||||
| import android.view.LayoutInflater | import android.view.LayoutInflater | ||||||
| import android.view.View | import android.view.View | ||||||
| import android.view.ViewGroup | import android.view.ViewGroup | ||||||
| import android.widget.ImageView | import android.widget.ImageView | ||||||
| import android.widget.TextView | import android.widget.TextView | ||||||
|  | import android.widget.Toast | ||||||
| import androidx.annotation.LayoutRes | import androidx.annotation.LayoutRes | ||||||
| import androidx.core.widget.ContentLoadingProgressBar | import androidx.core.widget.ContentLoadingProgressBar | ||||||
| import androidx.recyclerview.widget.RecyclerView | import androidx.recyclerview.widget.RecyclerView | ||||||
|  | @ -174,6 +176,15 @@ class EpisodeAdapter( | ||||||
|                 episodeDescript?.visibility = View.GONE |                 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 { |             episodeHolder.setOnClickListener { | ||||||
|                 clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) |                 clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) | ||||||
|             } |             } | ||||||
|  | @ -197,7 +208,15 @@ class EpisodeAdapter( | ||||||
|                 downloadButton.setUpButton( |                 downloadButton.setUpButton( | ||||||
|                     downloadInfo?.fileLength, downloadInfo?.totalBytes, episodeDownloadBar, episodeDownloadImage, null, |                     downloadInfo?.fileLength, downloadInfo?.totalBytes, episodeDownloadBar, episodeDownloadImage, null, | ||||||
|                     VideoDownloadHelper.DownloadEpisodeCached( |                     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) { |                     if (it.action == DOWNLOAD_ACTION_DOWNLOAD) { | ||||||
|  |  | ||||||
|  | @ -155,12 +155,4 @@ object AppUtils { | ||||||
|         } |         } | ||||||
|         return currentAudioFocusRequest |         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("/", "") |  | ||||||
|     } |  | ||||||
| } | } | ||||||
							
								
								
									
										5
									
								
								app/src/main/res/color/check_selection_color.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/color/check_selection_color.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <item android:state_activated="true" android:color="?attr/textColor"/> | ||||||
|  |     <item android:color="@color/transparent"/> | ||||||
|  | </selector> | ||||||
							
								
								
									
										5
									
								
								app/src/main/res/color/text_selection_color.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/color/text_selection_color.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <item android:state_activated="true" android:color="?attr/textColor"/> | ||||||
|  |     <item android:color="?attr/grayTextColor"/> | ||||||
|  | </selector> | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | <FrameLayout | ||||||
|  |         xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|         xmlns:app="http://schemas.android.com/apk/res-auto" |         xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|         xmlns:tools="http://schemas.android.com/tools" |         xmlns:tools="http://schemas.android.com/tools" | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|  | @ -8,7 +9,8 @@ | ||||||
|         android:screenOrientation="landscape" |         android:screenOrientation="landscape" | ||||||
|         tools:orientation="vertical" |         tools:orientation="vertical" | ||||||
| > | > | ||||||
|     <View android:layout_width="match_parent" |     <View | ||||||
|  |             android:layout_width="match_parent" | ||||||
|             android:layout_height="match_parent" |             android:layout_height="match_parent" | ||||||
|             android:id="@+id/shadow_overlay" |             android:id="@+id/shadow_overlay" | ||||||
|             android:background="@color/black_overlay" |             android:background="@color/black_overlay" | ||||||
|  | @ -383,7 +385,7 @@ | ||||||
|                         android:textColor="@android:color/white" |                         android:textColor="@android:color/white" | ||||||
|                         android:textSize="14sp" |                         android:textSize="14sp" | ||||||
|                         android:textStyle="normal"/> |                         android:textStyle="normal"/> | ||||||
| <!--app:buffered_color="@color/videoCache"--> |                 <!--app:buffered_color="@color/videoCache"--> | ||||||
|                 <com.google.android.exoplayer2.ui.DefaultTimeBar |                 <com.google.android.exoplayer2.ui.DefaultTimeBar | ||||||
|                         android:id="@id/exo_progress" |                         android:id="@id/exo_progress" | ||||||
|                         android:layout_width="0dp" |                         android:layout_width="0dp" | ||||||
|  | @ -471,7 +473,8 @@ | ||||||
| 
 | 
 | ||||||
|                     </LinearLayout> |                     </LinearLayout> | ||||||
|                 </androidx.cardview.widget.CardView> |                 </androidx.cardview.widget.CardView> | ||||||
|                 <LinearLayout android:id="@+id/lock_holder" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="match_parent"> |                 <LinearLayout android:id="@+id/lock_holder" android:orientation="horizontal" | ||||||
|  |                               android:layout_width="wrap_content" android:layout_height="match_parent"> | ||||||
|                     <androidx.cardview.widget.CardView |                     <androidx.cardview.widget.CardView | ||||||
|                             xmlns:card_view="http://schemas.android.com/apk/res-auto" |                             xmlns:card_view="http://schemas.android.com/apk/res-auto" | ||||||
|                             android:layout_width="wrap_content" |                             android:layout_width="wrap_content" | ||||||
|  |  | ||||||
							
								
								
									
										131
									
								
								app/src/main/res/layout/player_select_source_and_subs.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								app/src/main/res/layout/player_select_source_and_subs.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,131 @@ | ||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout | ||||||
|  |         xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |         xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |         android:orientation="vertical" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:background="@null" | ||||||
|  |         android:layout_height="match_parent"> | ||||||
|  |     <LinearLayout | ||||||
|  |             android:orientation="horizontal" | ||||||
|  |             android:layout_marginBottom="60dp" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="match_parent" | ||||||
|  |             android:baselineAligned="false"> | ||||||
|  | 
 | ||||||
|  |         <LinearLayout | ||||||
|  |                 android:layout_width="0dp" | ||||||
|  |                 android:layout_height="wrap_content" | ||||||
|  |                 android:orientation="vertical" | ||||||
|  |                 android:layout_weight="50"> | ||||||
|  |             <TextView | ||||||
|  |                     android:paddingStart="?android:attr/listPreferredItemPaddingStart" | ||||||
|  |                     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" | ||||||
|  |                     android:layout_marginTop="20dp" | ||||||
|  |                     android:layout_marginBottom="10dp" | ||||||
|  |                     android:textStyle="bold" | ||||||
|  |                     android:text="@string/pick_source" | ||||||
|  |                     android:textSize="20sp" | ||||||
|  |                     android:textColor="?attr/textColor" | ||||||
|  |                     android:layout_width="match_parent" | ||||||
|  |                     android:layout_rowWeight="1" | ||||||
|  |                     android:layout_height="wrap_content"> | ||||||
|  |             </TextView> | ||||||
|  |             <ListView | ||||||
|  |                     android:layout_marginTop="-10dp" | ||||||
|  |                     android:paddingTop="10dp" | ||||||
|  |                     android:id="@+id/sort_providers" | ||||||
|  |                     android:background="?attr/bitDarkerGrayBackground" | ||||||
|  |                     tools:listitem="@layout/sort_bottom_single_choice" | ||||||
|  |                     android:layout_width="match_parent" | ||||||
|  |                     android:layout_height="match_parent" | ||||||
|  |                     android:layout_rowWeight="1" | ||||||
|  |             /> | ||||||
|  |         </LinearLayout> | ||||||
|  |         <LinearLayout | ||||||
|  |                 android:id="@+id/sort_subtitles_holder" | ||||||
|  |                 android:layout_width="0dp" | ||||||
|  |                 android:layout_height="wrap_content" | ||||||
|  |                 android:orientation="vertical" | ||||||
|  |                 android:layout_weight="50"> | ||||||
|  |             <TextView | ||||||
|  |                     android:paddingStart="?android:attr/listPreferredItemPaddingStart" | ||||||
|  |                     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" | ||||||
|  |                     android:layout_marginTop="20dp" | ||||||
|  |                     android:layout_marginBottom="10dp" | ||||||
|  |                     android:textStyle="bold" | ||||||
|  |                     android:text="@string/pick_subtitle" | ||||||
|  |                     android:textSize="20sp" | ||||||
|  |                     android:textColor="?attr/textColor" | ||||||
|  |                     android:layout_rowWeight="1" | ||||||
|  |                     android:layout_width="match_parent" | ||||||
|  |                     android:layout_height="wrap_content"> | ||||||
|  |             </TextView> | ||||||
|  |             <ListView | ||||||
|  |                     android:layout_marginTop="-10dp" | ||||||
|  |                     android:paddingTop="10dp" | ||||||
|  |                     android:id="@+id/sort_subtitles" | ||||||
|  |                     android:background="?attr/bitDarkerGrayBackground" | ||||||
|  |                     tools:listitem="@layout/sort_bottom_single_choice" | ||||||
|  |                     android:layout_width="match_parent" | ||||||
|  |                     android:layout_rowWeight="1" | ||||||
|  |                     android:layout_height="match_parent"> | ||||||
|  |             </ListView> | ||||||
|  |         </LinearLayout> | ||||||
|  |     </LinearLayout> | ||||||
|  | 
 | ||||||
|  |     <LinearLayout | ||||||
|  |             android:orientation="horizontal" | ||||||
|  |             android:layout_gravity="bottom" | ||||||
|  |             android:gravity="bottom|end" | ||||||
|  |             android:layout_marginTop="-60dp" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="60dp"> | ||||||
|  | 
 | ||||||
|  |         <com.google.android.material.button.MaterialButton | ||||||
|  |                 android:layout_gravity="center_vertical|end" | ||||||
|  |                 android:layout_height="50dp" | ||||||
|  |                 android:layout_margin="5dp" | ||||||
|  | 
 | ||||||
|  |                 android:visibility="visible" | ||||||
|  |                 android:textStyle="bold" | ||||||
|  |                 app:rippleColor="?attr/grayBackground" | ||||||
|  |                 android:textColor="?attr/grayBackground" | ||||||
|  |                 app:iconTint="?attr/grayBackground" | ||||||
|  |                 android:textAllCaps="false" | ||||||
|  |                 app:iconGravity="textStart" | ||||||
|  |                 app:strokeColor="?attr/grayBackground" | ||||||
|  |                 app:backgroundTint="?attr/textColor" | ||||||
|  | 
 | ||||||
|  |                 app:iconSize="20dp" | ||||||
|  |                 android:text="@string/sort_apply" | ||||||
|  |                 android:id="@+id/pick_source_apply" | ||||||
|  |                 android:textSize="15sp" | ||||||
|  |                 app:cornerRadius="5dp" | ||||||
|  |                 android:layout_width="wrap_content" | ||||||
|  |         > | ||||||
|  |         </com.google.android.material.button.MaterialButton> | ||||||
|  |         <com.google.android.material.button.MaterialButton | ||||||
|  |                 android:layout_gravity="center_vertical|end" | ||||||
|  |                 android:layout_height="50dp" | ||||||
|  |                 android:layout_margin="5dp" | ||||||
|  | 
 | ||||||
|  |                 app:iconGravity="textStart" | ||||||
|  |                 app:strokeColor="?attr/textColor" | ||||||
|  |                 android:backgroundTint="?attr/grayBackground" | ||||||
|  |                 app:rippleColor="?attr/textColor" | ||||||
|  |                 android:textColor="?attr/textColor" | ||||||
|  |                 app:iconTint="?attr/textColor" | ||||||
|  |                 android:textAllCaps="false" | ||||||
|  |                 android:textStyle="bold" | ||||||
|  | 
 | ||||||
|  |                 app:iconSize="20dp" | ||||||
|  |                 android:text="@string/sort_cancel" | ||||||
|  |                 android:id="@+id/pick_source_cancel" | ||||||
|  |                 android:textSize="15sp" | ||||||
|  |                 app:cornerRadius="5dp" | ||||||
|  |                 android:layout_width="wrap_content" | ||||||
|  |         > | ||||||
|  |         </com.google.android.material.button.MaterialButton> | ||||||
|  |     </LinearLayout> | ||||||
|  | </LinearLayout> | ||||||
|  | @ -22,11 +22,11 @@ | ||||||
|                 android:layout_height="wrap_content"> |                 android:layout_height="wrap_content"> | ||||||
|             <!--app:cardCornerRadius="@dimen/roundedImageRadius"--> |             <!--app:cardCornerRadius="@dimen/roundedImageRadius"--> | ||||||
|             <androidx.cardview.widget.CardView |             <androidx.cardview.widget.CardView | ||||||
| 
 |  | ||||||
|                     android:layout_width="126dp" |                     android:layout_width="126dp" | ||||||
|                     android:layout_height="72dp" |                     android:layout_height="72dp" | ||||||
|             > |             > | ||||||
|                 <ImageView |                 <ImageView | ||||||
|  |                         android:foreground="?android:attr/selectableItemBackground" | ||||||
|                         android:id="@+id/episode_poster" |                         android:id="@+id/episode_poster" | ||||||
|                         tools:src="@drawable/example_poster" |                         tools:src="@drawable/example_poster" | ||||||
|                         android:scaleType="centerCrop" |                         android:scaleType="centerCrop" | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| <CheckedTextView | <!--<CheckedTextView | ||||||
|         xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" |         xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" | ||||||
|         android:id="@android:id/text1" |         android:id="@android:id/text1" | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|  | @ -11,3 +11,27 @@ | ||||||
|         android:checkMark="?android:attr/listChoiceIndicatorSingle" |         android:checkMark="?android:attr/listChoiceIndicatorSingle" | ||||||
|         android:paddingStart="?android:attr/listPreferredItemPaddingStart" |         android:paddingStart="?android:attr/listPreferredItemPaddingStart" | ||||||
|         android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"/> |         android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"/> | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | <TextView | ||||||
|  |         xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |         style="@style/AppTextViewStyle" | ||||||
|  |         android:id="@android:id/text1" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:minHeight="?android:attr/listPreferredItemHeightSmall" | ||||||
|  |         android:textAppearance="?android:attr/textAppearanceSmall" | ||||||
|  |         android:textColor="@color/text_selection_color" | ||||||
|  |         android:textSize="16sp" | ||||||
|  |         android:textStyle="bold" | ||||||
|  |         android:gravity="center_vertical" | ||||||
|  |         android:paddingStart="12dp" | ||||||
|  |         android:paddingEnd="7dip" | ||||||
|  |         tools:text="TEST" | ||||||
|  |         android:checkMark="?android:attr/listChoiceIndicatorSingle" | ||||||
|  |         android:ellipsize="marquee" | ||||||
|  |         android:drawableStart="@drawable/ic_baseline_check_24" | ||||||
|  |         android:drawableTint="@color/check_selection_color" | ||||||
|  |         tools:drawableTint="?attr/textColor" | ||||||
|  |         android:drawablePadding="20dp" | ||||||
|  | /> | ||||||
|  |  | ||||||
|  | @ -66,4 +66,7 @@ | ||||||
|     <string name="filter_bookmarks">Filter Bookmarks</string> |     <string name="filter_bookmarks">Filter Bookmarks</string> | ||||||
|     <string name="error_bookmarks_text">Bookmarks</string> |     <string name="error_bookmarks_text">Bookmarks</string> | ||||||
|     <string name="action_remove_from_bookmarks">Remove</string> |     <string name="action_remove_from_bookmarks">Remove</string> | ||||||
|  |     <string name="play_episode_toast">Play Episode</string> | ||||||
|  |     <string name="sort_apply">Apply</string> | ||||||
|  |     <string name="sort_cancel">Cancel</string> | ||||||
| </resources> | </resources> | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue