play any stream & a bit of work on invideoplayer episodes & no links found last error

This commit is contained in:
LagradOst 2022-07-16 03:43:10 +02:00
parent 6a3df3b412
commit 524d0717e2
21 changed files with 637 additions and 45 deletions

View file

@ -23,7 +23,7 @@ class GMPlayer : ExtractorApi() {
"origin" to mainUrl "origin" to mainUrl
), ),
data = mapOf("hash" to id, "r" to ref) data = mapOf("hash" to id, "r" to ref)
).also { println("shiv " + it.text) }.parsed<GmResponse>().videoSource ?: return null ).parsed<GmResponse>().videoSource ?: return null
return M3u8Helper.generateM3u8( return M3u8Helper.generateM3u8(
name, name,

View file

@ -1,30 +1,42 @@
package com.lagradost.cloudstream3.ui.download package com.lagradost.cloudstream3.ui.download
import android.app.Dialog
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.os.Bundle import android.os.Bundle
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.LinearLayout import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.isMovieType import com.lagradost.cloudstream3.isMovieType
import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.LinkGenerator
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
import com.lagradost.cloudstream3.utils.DataStore import com.lagradost.cloudstream3.utils.DataStore
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.android.synthetic.main.fragment_downloads.* import kotlinx.android.synthetic.main.fragment_downloads.*
import kotlinx.android.synthetic.main.stream_input.*
const val DOWNLOAD_NAVIGATE_TO = "downloadpage" const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
@ -168,9 +180,45 @@ class DownloadFragment : Fragment() {
download_list?.adapter = adapter download_list?.adapter = adapter
download_list?.layoutManager = GridLayoutManager(context, 1) download_list?.layoutManager = GridLayoutManager(context, 1)
/*download_stream_button?.isGone = context?.isTvSettings() == true download_stream_button?.isGone = context?.isTvSettings() == true
download_stream_button?.setOnClickListener { download_stream_button?.setOnClickListener {
val dialog =
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
dialog.setContentView(R.layout.stream_input)
dialog.show()
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt(
0
)?.text?.toString()?.let { copy ->
dialog.stream_url?.setText(copy)
}
dialog.apply_btt?.setOnClickListener {
val url = dialog.stream_url.text?.toString()
if (url.isNullOrEmpty()) {
showToast(activity, R.string.error_invalid_url, Toast.LENGTH_SHORT)
} else {
val referer = dialog.stream_referer.text?.toString()
activity?.navigate(
R.id.global_to_navigation_player,
GeneratorPlayer.newInstance(
LinkGenerator(
listOf(url),
extract = true,
referer = referer
)
)
)
dialog.dismissSafe(activity)
}
}
dialog.cancel_btt?.setOnClickListener {
dialog.dismissSafe(activity)
}
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
download_list?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> download_list?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
@ -181,7 +229,7 @@ class DownloadFragment : Fragment() {
download_stream_button?.extend() // show download_stream_button?.extend() // show
} }
} }
}*/ }
downloadsViewModel.updateList(requireContext()) downloadsViewModel.updateList(requireContext())
context?.fixPaddingStatusbar(download_root) context?.fixPaddingStatusbar(download_root)

View file

@ -40,6 +40,7 @@ import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.AppUtils.requestLocalAudioFocus import com.lagradost.cloudstream3.utils.AppUtils.requestLocalAudioFocus
import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import kotlinx.android.synthetic.main.fragment_player.* import kotlinx.android.synthetic.main.fragment_player.*
import kotlinx.android.synthetic.main.player_custom_layout.* import kotlinx.android.synthetic.main.player_custom_layout.*
@ -211,6 +212,10 @@ abstract class AbstractPlayerFragment(
} }
} }
open fun hasNextMirror(): Boolean {
throw NotImplementedError()
}
open fun nextMirror() { open fun nextMirror() {
throw NotImplementedError() throw NotImplementedError()
} }
@ -222,6 +227,23 @@ abstract class AbstractPlayerFragment(
} }
open fun playerError(exception: Exception) { open fun playerError(exception: Exception) {
fun showToast(message: String, gotoNext: Boolean = false) {
if (!gotoNext || hasNextMirror()) {
showToast(
activity,
message,
Toast.LENGTH_SHORT
)
} else {
showToast(
activity,
context?.getString(R.string.no_links_found_toast) + "\n" + message,
Toast.LENGTH_LONG
)
activity?.popCurrentPage()
}
}
val ctx = context ?: return val ctx = context ?: return
when (exception) { when (exception) {
is PlaybackException -> { is PlaybackException -> {
@ -230,47 +252,43 @@ abstract class AbstractPlayerFragment(
when (val code = exception.errorCode) { when (val code = exception.errorCode) {
PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED, PlaybackException.ERROR_CODE_IO_NO_PERMISSION, PlaybackException.ERROR_CODE_IO_UNSPECIFIED -> { PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED, PlaybackException.ERROR_CODE_IO_NO_PERMISSION, PlaybackException.ERROR_CODE_IO_UNSPECIFIED -> {
showToast( showToast(
activity,
"${ctx.getString(R.string.source_error)}\n$errorName ($code)\n$msg", "${ctx.getString(R.string.source_error)}\n$errorName ($code)\n$msg",
Toast.LENGTH_SHORT gotoNext = true
) )
nextMirror()
} }
PlaybackException.ERROR_CODE_REMOTE_ERROR, PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, PlaybackException.ERROR_CODE_TIMEOUT, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE -> { PlaybackException.ERROR_CODE_REMOTE_ERROR, PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, PlaybackException.ERROR_CODE_TIMEOUT, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE -> {
showToast( showToast(
activity,
"${ctx.getString(R.string.remote_error)}\n$errorName ($code)\n$msg", "${ctx.getString(R.string.remote_error)}\n$errorName ($code)\n$msg",
Toast.LENGTH_SHORT gotoNext = true
) )
nextMirror()
} }
PlaybackException.ERROR_CODE_DECODING_FAILED, PlaybackErrorEvent.ERROR_AUDIO_TRACK_INIT_FAILED, PlaybackErrorEvent.ERROR_AUDIO_TRACK_OTHER, PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED -> { PlaybackException.ERROR_CODE_DECODING_FAILED, PlaybackErrorEvent.ERROR_AUDIO_TRACK_INIT_FAILED, PlaybackErrorEvent.ERROR_AUDIO_TRACK_OTHER, PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED -> {
showToast( showToast(
activity,
"${ctx.getString(R.string.render_error)}\n$errorName ($code)\n$msg", "${ctx.getString(R.string.render_error)}\n$errorName ($code)\n$msg",
Toast.LENGTH_SHORT gotoNext = true
) )
nextMirror()
} }
else -> { else -> {
showToast( showToast(
activity,
"${ctx.getString(R.string.unexpected_error)}\n$errorName ($code)\n$msg", "${ctx.getString(R.string.unexpected_error)}\n$errorName ($code)\n$msg",
Toast.LENGTH_SHORT gotoNext = false
) )
} }
} }
} }
is InvalidFileException -> { is InvalidFileException -> {
showToast( showToast(
activity,
"${ctx.getString(R.string.source_error)}\n${exception.message}", "${ctx.getString(R.string.source_error)}\n${exception.message}",
Toast.LENGTH_SHORT gotoNext = true
) )
nextMirror()
} }
else -> { else -> {
showToast(activity, exception.message, Toast.LENGTH_SHORT) exception.message?.let {
showToast(
it,
gotoNext = false
)
}
} }
} }
} }

View file

@ -46,6 +46,10 @@ class DownloadFileGenerator(
return episodes.getOrNull(currentIndex + offset) return episodes.getOrNull(currentIndex + offset)
} }
override fun getAll(): List<Any>? {
return null
}
override suspend fun generateLinks( override suspend fun generateLinks(
clearCache: Boolean, clearCache: Boolean,
isCasting: Boolean, isCasting: Boolean,

View file

@ -94,6 +94,17 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
protected var isShowing = false protected var isShowing = false
protected var isLocked = false protected var isLocked = false
//private var episodes: List<Any> = listOf()
protected fun setEpisodes(ep: List<Any>) {
//hasEpisodes = ep.size > 1 // if has 2 episodes or more because you dont want to switch to your current episode
//(player_episode_list?.adapter as? PlayerEpisodeAdapter?)?.updateList(ep)
}
protected var hasEpisodes = false
private set
//protected val hasEpisodes
// get() = episodes.isNotEmpty()
// options for player // options for player
protected var currentPrefQuality = protected var currentPrefQuality =
Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell
@ -541,6 +552,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
player_pause_play?.startAnimation(fadeAnimation) player_pause_play?.startAnimation(fadeAnimation)
player_ffwd_holder?.startAnimation(fadeAnimation) player_ffwd_holder?.startAnimation(fadeAnimation)
player_rew_holder?.startAnimation(fadeAnimation) player_rew_holder?.startAnimation(fadeAnimation)
//if (hasEpisodes)
// player_episodes_button?.startAnimation(fadeAnimation)
//player_media_route_button?.startAnimation(fadeAnimation) //player_media_route_button?.startAnimation(fadeAnimation)
//video_bar.startAnimation(fadeAnimation) //video_bar.startAnimation(fadeAnimation)
@ -575,6 +589,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
player_pause_play?.isGone = isGone player_pause_play?.isGone = isGone
//player_buffering?.isGone = isGone //player_buffering?.isGone = isGone
player_top_holder?.isGone = isGone player_top_holder?.isGone = isGone
//player_episodes_button?.isVisible = !isGone && hasEpisodes
player_video_title?.isGone = togglePlayerTitleGone player_video_title?.isGone = togglePlayerTitleGone
player_video_title_rez?.isGone = isGone player_video_title_rez?.isGone = isGone
player_episode_filler?.isGone = isGone player_episode_filler?.isGone = isGone
@ -750,7 +765,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
// validates if the touch is inside of the player area // validates if the touch is inside of the player area
isCurrentTouchValid = isValidTouch(currentTouch.x, currentTouch.y) isCurrentTouchValid = isValidTouch(currentTouch.x, currentTouch.y)
if (isCurrentTouchValid) { /*if (isCurrentTouchValid && player_episode_list?.isVisible == true) {
player_episode_list?.isVisible = false
} else*/ if (isCurrentTouchValid) {
currentTouchStartTime = System.currentTimeMillis() currentTouchStartTime = System.currentTimeMillis()
currentTouchStart = currentTouch currentTouchStart = currentTouch
currentTouchLast = currentTouch currentTouchLast = currentTouch
@ -1152,6 +1169,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} else false } else false
} }
//player_episodes_button?.setOnClickListener {
// player_episodes_button?.isGone = true
// player_episode_list?.isVisible = true
//}
//
//player_episode_list?.adapter = PlayerEpisodeAdapter { click ->
//
//}
try { try {
context?.let { ctx -> context?.let { ctx ->
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)

View file

@ -143,6 +143,7 @@ class GeneratorPlayer : FullScreenPlayer() {
currentSelectedLink = link currentSelectedLink = link
currentMeta = viewModel.getMeta() currentMeta = viewModel.getMeta()
nextMeta = viewModel.getNextMeta() nextMeta = viewModel.getNextMeta()
setEpisodes(viewModel.getAllMeta() ?: emptyList())
isActive = true isActive = true
setPlayerDimen(null) setPlayerDimen(null)
setTitle() setTitle()
@ -273,8 +274,10 @@ class GeneratorPlayer : FullScreenPlayer() {
mainTextView?.text = item?.let { getName(it, false) } mainTextView?.text = item?.let { getName(it, false) }
val language = item?.let { fromTwoLettersToLanguage(it.lang.trim()) ?: it.lang } ?: "" val language =
val providerSuffix = if (isSingleProvider || item == null) "" else " · ${item.source}" item?.let { fromTwoLettersToLanguage(it.lang.trim()) ?: it.lang } ?: ""
val providerSuffix =
if (isSingleProvider || item == null) "" else " · ${item.source}"
secondaryTextView?.text = language + providerSuffix secondaryTextView?.text = language + providerSuffix
setHearingImpairedIcon(drawableEnd, position) setHearingImpairedIcon(drawableEnd, position)
@ -688,6 +691,11 @@ class GeneratorPlayer : FullScreenPlayer() {
viewModel.loadLinksPrev() viewModel.loadLinksPrev()
} }
override fun hasNextMirror(): Boolean {
val links = sortLinks()
return links.isNotEmpty() && links.indexOf(currentSelectedLink) + 1 < links.size
}
override fun nextMirror() { override fun nextMirror() {
val links = sortLinks() val links = sortLinks()
if (links.isEmpty()) { if (links.isEmpty()) {

View file

@ -14,6 +14,7 @@ interface IGenerator {
fun getCurrentId(): Int? // this is used to save data or read data about this id fun getCurrentId(): Int? // this is used to save data or read data about this id
fun getCurrent(offset : Int = 0): Any? // this is used to get metadata about the current playing, can return null fun getCurrent(offset : Int = 0): Any? // this is used to get metadata about the current playing, can return null
fun getAll() : List<Any>? // thus us used to get the metadata about all entries, not needed
/* not safe, must use try catch */ /* not safe, must use try catch */
suspend fun generateLinks( suspend fun generateLinks(

View file

@ -0,0 +1,68 @@
package com.lagradost.cloudstream3.ui.player
import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.loadExtractor
class LinkGenerator(
private val links: List<String>,
private val extract: Boolean = true,
private val referer: String? = null,
) : IGenerator {
override val hasCache = false
override fun getCurrentId(): Int? {
return null
}
override fun hasNext(): Boolean {
return false
}
override fun getAll(): List<Any>? {
return null
}
override fun hasPrev(): Boolean {
return false
}
override fun getCurrent(offset: Int): Any? {
return null
}
override fun goto(index: Int) {}
override fun next() {}
override fun prev() {}
override suspend fun generateLinks(
clearCache: Boolean,
isCasting: Boolean,
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
subtitleCallback: (SubtitleData) -> Unit,
offset: Int
): Boolean {
links.apmap { link ->
if (!extract || !loadExtractor(link, referer) {
callback(it to null)
}) {
// if don't extract or if no extractor found simply return the link
callback(
ExtractorLink(
"",
link,
link,
referer ?: "",
Qualities.Unknown.value, link.contains(".m3u8") // TODO USE REAL PARSER
) to null
)
}
}
return true
}
}

View file

@ -0,0 +1,158 @@
package com.lagradost.cloudstream3.ui.player
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.ContentLoadingProgressBar
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.ui.result.getDisplayPosition
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.player_episodes_large.view.episode_holder_large
import kotlinx.android.synthetic.main.player_episodes_large.view.episode_progress
import kotlinx.android.synthetic.main.player_episodes_small.view.episode_holder
import kotlinx.android.synthetic.main.result_episode_large.view.*
data class PlayerEpisodeClickEvent(val action: Int, val data: Any)
class PlayerEpisodeAdapter(
private val items: MutableList<Any> = mutableListOf(),
private val clickCallback: (PlayerEpisodeClickEvent) -> Unit,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return PlayerEpisodeCardViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.player_episodes, parent, false),
clickCallback,
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
println("HOLDER $holder $position")
when (holder) {
is PlayerEpisodeCardViewHolder -> {
holder.bind(items[position])
}
}
}
override fun getItemCount(): Int {
return items.size
}
fun updateList(newList: List<Any>) {
println("Updated list $newList")
val diffResult = DiffUtil.calculateDiff(EpisodeDiffCallback(this.items, newList))
items.clear()
items.addAll(newList)
diffResult.dispatchUpdatesTo(this)
}
class PlayerEpisodeCardViewHolder
constructor(
itemView: View,
private val clickCallback: (PlayerEpisodeClickEvent) -> Unit,
) : RecyclerView.ViewHolder(itemView) {
@SuppressLint("SetTextI18n")
fun bind(card: Any) {
if (card is ResultEpisode) {
val (parentView, otherView) = if (card.poster == null) {
itemView.episode_holder to itemView.episode_holder_large
} else {
itemView.episode_holder_large to itemView.episode_holder
}
val episodeText: TextView? = parentView.episode_text
val episodeFiller: MaterialButton? = parentView.episode_filler
val episodeRating: TextView? = parentView.episode_rating
val episodeDescript: TextView? = parentView.episode_descript
val episodeProgress: ContentLoadingProgressBar? = parentView.episode_progress
val episodePoster: ImageView? = parentView.episode_poster
parentView.isVisible = true
otherView.isVisible = false
episodeText?.apply {
val name =
if (card.name == null) "${context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
text = name
isSelected = true
}
episodeFiller?.isVisible = card.isFiller == true
val displayPos = card.getDisplayPosition()
episodeProgress?.max = (card.duration / 1000).toInt()
episodeProgress?.progress = (displayPos / 1000).toInt()
episodeProgress?.isVisible = displayPos > 0L
episodePoster?.isVisible = episodePoster?.setImage(card.poster) == true
if (card.rating != null) {
episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format)
?.format(card.rating.toFloat() / 10f)
} else {
episodeRating?.text = ""
}
episodeRating?.isGone = episodeRating?.text.isNullOrBlank()
episodeDescript?.apply {
text = card.description.html()
isGone = text.isNullOrBlank()
//setOnClickListener {
// clickCallback.invoke(PlayerEpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
//}
}
parentView.setOnClickListener {
clickCallback.invoke(PlayerEpisodeClickEvent(0, card))
}
if (parentView.context.isTrueTvSettings()) {
parentView.isFocusable = true
parentView.isFocusableInTouchMode = true
parentView.touchscreenBlocksFocus = false
}
}
}
}
}
class EpisodeDiffCallback(
private val oldList: List<Any>,
private val newList: List<Any>
) :
DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val a = oldList[oldItemPosition]
val b = newList[newItemPosition]
return if (a is ResultEpisode && b is ResultEpisode) {
a.id == b.id
} else {
a == b
}
}
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition]
}

View file

@ -80,6 +80,10 @@ class PlayerGeneratorViewModel : ViewModel() {
return normalSafeApiCall { generator?.getCurrent() } return normalSafeApiCall { generator?.getCurrent() }
} }
fun getAllMeta(): List<Any>? {
return normalSafeApiCall { generator?.getAll() }
}
fun getNextMeta(): Any? { fun getNextMeta(): Any? {
return normalSafeApiCall { return normalSafeApiCall {
if (generator?.hasNext() == false) return@normalSafeApiCall null if (generator?.hasNext() == false) return@normalSafeApiCall null

View file

@ -15,7 +15,8 @@ class RepoLinkGenerator(
) : IGenerator { ) : IGenerator {
companion object { companion object {
const val TAG = "RepoLink" const val TAG = "RepoLink"
val cache: HashMap<Pair<String, Int>, Pair<MutableSet<ExtractorLink>, MutableSet<SubtitleData>>> = hashMapOf() val cache: HashMap<Pair<String, Int>, Pair<MutableSet<ExtractorLink>, MutableSet<SubtitleData>>> =
hashMapOf()
} }
override val hasCache = true override val hasCache = true
@ -54,6 +55,10 @@ class RepoLinkGenerator(
return episodes.getOrNull(currentIndex + offset) return episodes.getOrNull(currentIndex + offset)
} }
override fun getAll(): List<Any> {
return episodes
}
// this is a simple array that is used to instantly load links if they are already loaded // this is a simple array that is used to instantly load links if they are already loaded
//var linkCache = Array<Set<ExtractorLink>>(size = episodes.size, init = { setOf() }) //var linkCache = Array<Set<ExtractorLink>>(size = episodes.size, init = { setOf() })
//var subsCache = Array<Set<SubtitleData>>(size = episodes.size, init = { setOf() }) //var subsCache = Array<Set<SubtitleData>>(size = episodes.size, init = { setOf() })

View file

@ -175,15 +175,9 @@ class EpisodeAdapter(
val displayPos = card.getDisplayPosition() val displayPos = card.getDisplayPosition()
episodeProgress?.max = (card.duration / 1000).toInt() episodeProgress?.max = (card.duration / 1000).toInt()
episodeProgress?.progress = (displayPos / 1000).toInt() episodeProgress?.progress = (displayPos / 1000).toInt()
episodeProgress?.isVisible = displayPos > 0L
episodeProgress?.visibility = if (displayPos > 0L) View.VISIBLE else View.GONE episodePoster?.isVisible = episodePoster?.setImage(card.poster) == true
if (card.poster != null) {
episodePoster?.visibility = View.VISIBLE
episodePoster?.setImage(card.poster)
} else {
episodePoster?.visibility = View.GONE
}
if (card.rating != null) { if (card.rating != null) {
episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format) episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format)

View file

@ -97,7 +97,10 @@ import kotlinx.android.synthetic.main.fragment_trailer.*
import kotlinx.android.synthetic.main.result_recommendations.* import kotlinx.android.synthetic.main.result_recommendations.*
import kotlinx.android.synthetic.main.result_sync.* import kotlinx.android.synthetic.main.result_sync.*
import kotlinx.android.synthetic.main.trailer_custom_layout.* import kotlinx.android.synthetic.main.trailer_custom_layout.*
import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -610,6 +613,10 @@ class ResultFragment : ResultTrailerPlayer() {
loadTrailer() loadTrailer()
} }
override fun hasNextMirror(): Boolean {
return currentTrailerIndex + 1 < currentTrailers.size
}
override fun playerError(exception: Exception) { override fun playerError(exception: Exception) {
if (player.getIsPlaying()) { // because we dont want random toasts in player if (player.getIsPlaying()) { // because we dont want random toasts in player
super.playerError(exception) super.playerError(exception)
@ -697,7 +704,8 @@ class ResultFragment : ResultTrailerPlayer() {
}?.also { text -> }?.also { text ->
result_next_airing_time?.text = text result_next_airing_time?.text = text
result_next_airing?.text = result_next_airing?.text =
ctx.getString(R.string.next_episode_format).format(nextAiring.episode) ctx.getString(R.string.next_episode_format)
.format(nextAiring.episode)
} != null } != null
} }
} catch (e: Exception) { // mistranslation } catch (e: Exception) { // mistranslation
@ -1163,7 +1171,7 @@ class ResultFragment : ResultTrailerPlayer() {
try { try {
acquireSingeExtractorLink(act.getString(R.string.episode_action_copy_link)) { link -> acquireSingeExtractorLink(act.getString(R.string.episode_action_copy_link)) { link ->
val serviceClipboard = val serviceClipboard =
(act.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager?) (act.getSystemService(CLIPBOARD_SERVICE) as? ClipboardManager?)
?: return@acquireSingeExtractorLink ?: return@acquireSingeExtractorLink
val clip = ClipData.newPlainText(link.name, link.url) val clip = ClipData.newPlainText(link.name, link.url)
serviceClipboard.setPrimaryClip(clip) serviceClipboard.setPrimaryClip(clip)

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M11.67,3.87L9.9,2.1 0,12l9.9,9.9 1.77,-1.77L3.54,12z"/>
</vector>

View file

@ -189,7 +189,6 @@
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:text="@string/stream" android:text="@string/stream"
android:visibility="gone"
android:id="@+id/download_stream_button" android:id="@+id/download_stream_button"
app:icon="@drawable/netflix_play" app:icon="@drawable/netflix_play"
style="@style/ExtendedFloatingActionButton" style="@style/ExtendedFloatingActionButton"

View file

@ -607,14 +607,55 @@
style="@android:style/Widget.Material.ProgressBar.Horizontal" style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="4dp" android:layout_width="4dp"
android:layout_height="150dp" android:layout_height="150dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
android:layout_marginEnd="40dp" android:layout_marginEnd="40dp"
android:indeterminate="false" android:indeterminate="false"
android:max="100" android:max="100"
android:progress="100" android:progress="100"
android:progressDrawable="@drawable/progress_drawable_vertical" /> android:progressDrawable="@drawable/progress_drawable_vertical"
android:layout_centerInParent="true" />
</RelativeLayout> </RelativeLayout>
</LinearLayout> </LinearLayout>
<!--
<ImageView
android:padding="15dp"
android:id="@+id/player_episodes_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_baseline_arrow_back_ios_24"
android:background="@drawable/video_tap_button_always_white"
app:tint="@android:color/white"
tools:ignore="ContentDescription"></ImageView>
<LinearLayout
android:visibility="gone"
android:background="?attr/primaryBlackBackground"
android:orientation="vertical"
android:layout_gravity="end"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:padding="10dp"
style="@style/WatchHeaderText"
android:textSize="15sp"
android:layout_marginEnd="0dp"
android:text="@string/episodes"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:requiresFadingEdge="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:id="@+id/player_episode_list"
tools:listitem="@layout/player_episodes"
android:layout_width="200dp"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
-->
</FrameLayout> </FrameLayout>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include android:visibility="gone" layout="@layout/player_episodes_small" />
<include android:visibility="gone" layout="@layout/player_episodes_large" />
</FrameLayout>

View file

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/episode_holder_large"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="@dimen/rounded_image_radius"
app:cardBackgroundColor="@color/transparent"
app:cardElevation="0dp"
android:foreground="@drawable/outline_drawable"
android:layout_marginBottom="5dp">
<!-- IDK BUT THIS DOES NOT SEAM LIKE A GOOD WAY OF DOING IT -->
<!--<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<View
android:layout_weight="0.5"
android:id="@+id/episode_view_procentage"
android:alpha="0.2"
android:background="?attr/colorPrimary"
android:layout_width="0dp"
android:layout_height="match_parent">
</View>
<View
android:id="@+id/episode_view_procentage_off"
android:layout_weight="0.10"
android:alpha="0"
android:background="@color/transparent"
android:layout_width="0dp"
android:layout_height="match_parent">
</View>
</LinearLayout>-->
<androidx.cardview.widget.CardView
android:layout_width="200dp"
android:layout_height="114dp"
android:foreground="@drawable/outline_drawable">
<ImageView
android:nextFocusLeft="@id/result_episode_download"
android:nextFocusRight="@id/episode_holder"
android:id="@+id/episode_poster"
tools:src="@drawable/example_poster"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/episode_poster_img_des" />
<ImageView
android:src="@drawable/play_button"
android:layout_gravity="center"
android:layout_width="57dp"
android:layout_height="57dp"
android:contentDescription="@string/play_episode" />
<androidx.core.widget.ContentLoadingProgressBar
android:layout_marginBottom="-1.5dp"
android:id="@+id/episode_progress"
android:progressTint="?attr/colorPrimary"
android:progressBackgroundTint="?attr/colorPrimary"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="match_parent"
tools:progress="50"
android:layout_gravity="bottom"
android:layout_height="5dp" />
</androidx.cardview.widget.CardView>
<!-- <LinearLayout
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.button.MaterialButton
android:layout_marginStart="10dp"
android:layout_gravity="center"
style="@style/SmallBlackButton"
android:text="@string/filler"
android:id="@+id/episode_filler" />
<TextView
android:id="@+id/episode_text"
android:layout_marginStart="10dp"
android:layout_marginEnd="50dp"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
tools:text="Episode 1"
android:textColor="?attr/textColor"
android:scrollHorizontally="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="0"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>-->
</LinearLayout>

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/episode_holder"
android:layout_width="match_parent"
android:layout_height="50dp"
app:cardBackgroundColor="@color/transparent"
app:cardElevation="0dp"
android:foreground="@drawable/outline_drawable">
<androidx.core.widget.ContentLoadingProgressBar
android:layout_marginBottom="-1.5dp"
android:id="@+id/episode_progress"
android:progressTint="?attr/colorPrimary"
android:progressBackgroundTint="?attr/colorPrimary"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="match_parent"
tools:progress="50"
android:layout_gravity="bottom"
android:layout_height="5dp" />
<LinearLayout
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--marquee_forever-->
<com.google.android.material.button.MaterialButton
android:layout_marginStart="10dp"
android:layout_gravity="center"
style="@style/SmallBlackButton"
android:text="@string/filler"
android:id="@+id/episode_filler" />
<TextView
android:id="@+id/episode_text"
android:layout_marginStart="10dp"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
tools:text="Episode 1"
android:textColor="?attr/textColor"
android:scrollHorizontally="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="0"
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</FrameLayout>

View file

@ -5,14 +5,46 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<LinearLayout
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text1"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:textStyle="bold"
android:textSize="20sp"
android:textColor="?attr/textColor"
android:layout_width="match_parent"
android:layout_rowWeight="1"
android:text="@string/stream"
android:layout_height="wrap_content" />
<EditText <EditText
android:layout_weight="20" android:layout_weight="20"
android:id="@+id/subtitle_offset_input" android:id="@+id/stream_url"
android:inputType="textUri" android:inputType="textUri"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/subtitle_offset_hint" android:hint="@string/network_adress_example"
tools:ignore="LabelFor" /> tools:ignore="LabelFor" />
<EditText
android:layout_weight="20"
android:id="@+id/stream_referer"
android:inputType="textUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/referer"
tools:ignore="LabelFor" />
</LinearLayout>
<LinearLayout <LinearLayout
android:orientation="horizontal" android:orientation="horizontal"
android:layout_gravity="bottom" android:layout_gravity="bottom"
@ -24,7 +56,7 @@
style="@style/WhiteButton" style="@style/WhiteButton"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
android:visibility="visible" android:visibility="visible"
android:text="@string/sort_apply" android:text="@string/home_play"
android:id="@+id/apply_btt" android:id="@+id/apply_btt"
android:layout_width="wrap_content"> android:layout_width="wrap_content">

View file

@ -546,9 +546,12 @@
<string name="resolution">Resolution</string> <string name="resolution">Resolution</string>
<string name="error_invalid_id">Invalid id</string> <string name="error_invalid_id">Invalid id</string>
<string name="error_invalid_data">Invalid data</string> <string name="error_invalid_data">Invalid data</string>
<string name="error_invalid_url">Invalid url</string>
<string name="error">Error</string> <string name="error">Error</string>
<string name="subtitles_remove_captions">Remove closed captions from subtitles</string> <string name="subtitles_remove_captions">Remove closed captions from subtitles</string>
<string name="subtitles_remove_bloat">Remove bloat from subtitles</string> <string name="subtitles_remove_bloat">Remove bloat from subtitles</string>
<string name="extras">Extras</string> <string name="extras">Extras</string>
<string name="trailer">Trailer</string> <string name="trailer">Trailer</string>
<string name="network_adress_example">Link to stream</string>
<string name="referer">Referer</string>
</resources> </resources>