mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
play any stream & a bit of work on invideoplayer episodes & no links found last error
This commit is contained in:
parent
6a3df3b412
commit
524d0717e2
21 changed files with 637 additions and 45 deletions
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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() })
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
8
app/src/main/res/layout/player_episodes.xml
Normal file
8
app/src/main/res/layout/player_episodes.xml
Normal 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>
|
106
app/src/main/res/layout/player_episodes_large.xml
Normal file
106
app/src/main/res/layout/player_episodes_large.xml
Normal 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>
|
56
app/src/main/res/layout/player_episodes_small.xml
Normal file
56
app/src/main/res/layout/player_episodes_small.xml
Normal 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>
|
|
@ -5,14 +5,46 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<EditText
|
<LinearLayout
|
||||||
android:layout_weight="20"
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
android:id="@+id/subtitle_offset_input"
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||||
android:inputType="textUri"
|
android:orientation="vertical"
|
||||||
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"
|
|
||||||
tools:ignore="LabelFor" />
|
<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
|
||||||
|
android:layout_weight="20"
|
||||||
|
android:id="@+id/stream_url"
|
||||||
|
android:inputType="textUri"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/network_adress_example"
|
||||||
|
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">
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue