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,6 +30,7 @@ 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>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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…
Reference in a new issue