This commit is contained in:
LagradOst 2021-08-04 03:50:24 +02:00
parent 6b27db036b
commit 3f8229756d
17 changed files with 450 additions and 101 deletions

View file

@ -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,

View file

@ -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()
} }
} }

View file

@ -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

View file

@ -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,

View file

@ -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(

View file

@ -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()
} }
} }

View file

@ -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(

View file

@ -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) {

View file

@ -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) {

View file

@ -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("/", "")
}
} }

View 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>

View 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>

View file

@ -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"

View 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>

View file

@ -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"

View file

@ -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"
/>

View file

@ -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>