forked from recloudstream/cloudstream
working
This commit is contained in:
parent
7619b7e9d9
commit
f77fe0a31e
9 changed files with 215 additions and 135 deletions
|
@ -1,15 +1,37 @@
|
||||||
package com.lagradost.cloudstream3.network
|
package com.lagradost.cloudstream3.network
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.USER_AGENT
|
import com.lagradost.cloudstream3.USER_AGENT
|
||||||
import com.lagradost.nicehttp.Requests
|
import com.lagradost.nicehttp.Requests
|
||||||
import com.lagradost.nicehttp.ignoreAllSSLErrors
|
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.Headers.Companion.toHeaders
|
import okhttp3.Headers.Companion.toHeaders
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.TrustManager
|
||||||
|
import javax.net.ssl.X509TrustManager
|
||||||
|
|
||||||
|
fun OkHttpClient.Builder.ignoreAllSSLErrors(): OkHttpClient.Builder {
|
||||||
|
val naiveTrustManager = @SuppressLint("CustomX509TrustManager")
|
||||||
|
object : X509TrustManager {
|
||||||
|
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
|
||||||
|
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) = Unit
|
||||||
|
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) = Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
val insecureSocketFactory = SSLContext.getInstance("SSL").apply {
|
||||||
|
val trustAllCerts = arrayOf<TrustManager>(naiveTrustManager)
|
||||||
|
init(null, trustAllCerts, SecureRandom())
|
||||||
|
}.socketFactory
|
||||||
|
|
||||||
|
sslSocketFactory(insecureSocketFactory, naiveTrustManager)
|
||||||
|
hostnameVerifier { _, _ -> true }
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
fun Requests.initClient(context: Context): OkHttpClient {
|
fun Requests.initClient(context: Context): OkHttpClient {
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
|
|
@ -108,6 +108,10 @@ abstract class AbstractPlayerFragment(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun onTimestampSkipped(timestamp: EpisodeSkip.SkipStamp) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
open fun exitedPipMode() {
|
open fun exitedPipMode() {
|
||||||
throw NotImplementedError()
|
throw NotImplementedError()
|
||||||
}
|
}
|
||||||
|
@ -379,7 +383,8 @@ abstract class AbstractPlayerFragment(
|
||||||
subtitlesUpdates = ::subtitlesChanged,
|
subtitlesUpdates = ::subtitlesChanged,
|
||||||
embeddedSubtitlesFetched = ::embeddedSubtitlesFetched,
|
embeddedSubtitlesFetched = ::embeddedSubtitlesFetched,
|
||||||
onTracksInfoChanged = ::onTracksInfoChanged,
|
onTracksInfoChanged = ::onTracksInfoChanged,
|
||||||
onTimestampInvoked = ::onTimestamp
|
onTimestampInvoked = ::onTimestamp,
|
||||||
|
onTimestampSkipped = ::onTimestampSkipped
|
||||||
)
|
)
|
||||||
|
|
||||||
if (player is CS3IPlayer) {
|
if (player is CS3IPlayer) {
|
||||||
|
|
|
@ -118,6 +118,7 @@ class CS3IPlayer : IPlayer {
|
||||||
private var embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null
|
private var embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null
|
||||||
private var onTracksInfoChanged: (() -> Unit)? = null
|
private var onTracksInfoChanged: (() -> Unit)? = null
|
||||||
private var onTimestampInvoked: ((EpisodeSkip.SkipStamp) -> Unit)? = null
|
private var onTimestampInvoked: ((EpisodeSkip.SkipStamp) -> Unit)? = null
|
||||||
|
private var onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)? = null
|
||||||
|
|
||||||
override fun releaseCallbacks() {
|
override fun releaseCallbacks() {
|
||||||
playerUpdated = null
|
playerUpdated = null
|
||||||
|
@ -133,6 +134,7 @@ class CS3IPlayer : IPlayer {
|
||||||
onTracksInfoChanged = null
|
onTracksInfoChanged = null
|
||||||
onTimestampInvoked = null
|
onTimestampInvoked = null
|
||||||
requestSubtitleUpdate = null
|
requestSubtitleUpdate = null
|
||||||
|
onTimestampSkipped = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initCallbacks(
|
override fun initCallbacks(
|
||||||
|
@ -149,6 +151,7 @@ class CS3IPlayer : IPlayer {
|
||||||
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)?,
|
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)?,
|
||||||
onTracksInfoChanged: (() -> Unit)?,
|
onTracksInfoChanged: (() -> Unit)?,
|
||||||
onTimestampInvoked: ((EpisodeSkip.SkipStamp) -> Unit)?,
|
onTimestampInvoked: ((EpisodeSkip.SkipStamp) -> Unit)?,
|
||||||
|
onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)?,
|
||||||
) {
|
) {
|
||||||
this.playerUpdated = playerUpdated
|
this.playerUpdated = playerUpdated
|
||||||
this.updateIsPlaying = updateIsPlaying
|
this.updateIsPlaying = updateIsPlaying
|
||||||
|
@ -163,6 +166,7 @@ class CS3IPlayer : IPlayer {
|
||||||
this.embeddedSubtitlesFetched = embeddedSubtitlesFetched
|
this.embeddedSubtitlesFetched = embeddedSubtitlesFetched
|
||||||
this.onTracksInfoChanged = onTracksInfoChanged
|
this.onTracksInfoChanged = onTracksInfoChanged
|
||||||
this.onTimestampInvoked = onTimestampInvoked
|
this.onTimestampInvoked = onTimestampInvoked
|
||||||
|
this.onTimestampSkipped = onTimestampSkipped
|
||||||
}
|
}
|
||||||
|
|
||||||
// I know, this is not a perfect solution, however it works for fixing subs
|
// I know, this is not a perfect solution, however it works for fixing subs
|
||||||
|
@ -801,8 +805,14 @@ class CS3IPlayer : IPlayer {
|
||||||
//val dur = this@CS3IPlayer.getDuration() ?: return@apply
|
//val dur = this@CS3IPlayer.getDuration() ?: return@apply
|
||||||
val pos = this@CS3IPlayer.getPosition() ?: return@apply
|
val pos = this@CS3IPlayer.getPosition() ?: return@apply
|
||||||
for (lastTimeStamp in lastTimeStamps) {
|
for (lastTimeStamp in lastTimeStamps) {
|
||||||
if(lastTimeStamp.startMs <= pos && pos < lastTimeStamp.endMs) {
|
if (lastTimeStamp.startMs <= pos && pos < lastTimeStamp.endMs) {
|
||||||
seekTo(lastTimeStamp.endMs)
|
if (lastTimeStamp.skipToNextEpisode) {
|
||||||
|
handleEvent(CSPlayerEvent.NextEpisode)
|
||||||
|
} else {
|
||||||
|
seekTo(lastTimeStamp.endMs)
|
||||||
|
}
|
||||||
|
|
||||||
|
onTimestampSkipped?.invoke(lastTimeStamp)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1029,12 +1039,14 @@ class CS3IPlayer : IPlayer {
|
||||||
override fun addTimeStamps(timeStamps: List<EpisodeSkip.SkipStamp>) {
|
override fun addTimeStamps(timeStamps: List<EpisodeSkip.SkipStamp>) {
|
||||||
lastTimeStamps = timeStamps
|
lastTimeStamps = timeStamps
|
||||||
timeStamps.forEach { timestamp ->
|
timeStamps.forEach { timestamp ->
|
||||||
|
println("ADDING: $timestamp")
|
||||||
exoPlayer?.createMessage { _, payload ->
|
exoPlayer?.createMessage { _, payload ->
|
||||||
|
if (payload is EpisodeSkip.SkipStamp) // this should always be true
|
||||||
|
onTimestampInvoked?.invoke(payload)
|
||||||
}
|
}
|
||||||
?.setLooper(Looper.getMainLooper())
|
?.setLooper(Looper.getMainLooper())
|
||||||
?.setPosition(timestamp.startMs)
|
?.setPosition(timestamp.startMs)
|
||||||
// .setPayload(customPayloadData)
|
?.setPayload(timestamp)
|
||||||
?.setDeleteAfterDelivery(false)
|
?.setDeleteAfterDelivery(false)
|
||||||
?.send()
|
?.send()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.lagradost.cloudstream3.ui.player
|
package com.lagradost.cloudstream3.ui.player
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -13,6 +14,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.animation.addListener
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
@ -49,6 +51,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import kotlinx.android.synthetic.main.dialog_online_subtitles.*
|
import kotlinx.android.synthetic.main.dialog_online_subtitles.*
|
||||||
import kotlinx.android.synthetic.main.dialog_online_subtitles.apply_btt
|
import kotlinx.android.synthetic.main.dialog_online_subtitles.apply_btt
|
||||||
import kotlinx.android.synthetic.main.dialog_online_subtitles.cancel_btt
|
import kotlinx.android.synthetic.main.dialog_online_subtitles.cancel_btt
|
||||||
|
@ -66,8 +69,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
Log.i(TAG, "newInstance = $syncData")
|
Log.i(TAG, "newInstance = $syncData")
|
||||||
lastUsedGenerator = generator
|
lastUsedGenerator = generator
|
||||||
return Bundle().apply {
|
return Bundle().apply {
|
||||||
if (syncData != null)
|
if (syncData != null) putSerializable("syncData", syncData)
|
||||||
putSerializable("syncData", syncData)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,9 +182,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
},
|
},
|
||||||
currentSubs,
|
currentSubs,
|
||||||
(if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle(
|
(if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle(
|
||||||
currentSubs,
|
currentSubs, settings = true, downloads = true
|
||||||
settings = true,
|
|
||||||
downloads = true
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -231,9 +231,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openOnlineSubPicker(
|
override fun openOnlineSubPicker(
|
||||||
context: Context,
|
context: Context, imdbId: Long?, dismissCallback: (() -> Unit)
|
||||||
imdbId: Long?,
|
|
||||||
dismissCallback: (() -> Unit)
|
|
||||||
) {
|
) {
|
||||||
val providers = subsProviders
|
val providers = subsProviders
|
||||||
val isSingleProvider = subsProviders.size == 1
|
val isSingleProvider = subsProviders.size == 1
|
||||||
|
@ -256,8 +254,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
val arrayAdapter =
|
val arrayAdapter =
|
||||||
object : ArrayAdapter<AbstractSubtitleEntities.SubtitleEntity>(dialog.context, layout) {
|
object : ArrayAdapter<AbstractSubtitleEntities.SubtitleEntity>(dialog.context, layout) {
|
||||||
fun setHearingImpairedIcon(
|
fun setHearingImpairedIcon(
|
||||||
imageViewEnd: ImageView?,
|
imageViewEnd: ImageView?, position: Int
|
||||||
position: Int
|
|
||||||
) {
|
) {
|
||||||
if (imageViewEnd == null) return
|
if (imageViewEnd == null) return
|
||||||
val isHearingImpaired =
|
val isHearingImpaired =
|
||||||
|
@ -265,13 +262,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
val drawableEnd = if (isHearingImpaired) {
|
val drawableEnd = if (isHearingImpaired) {
|
||||||
ContextCompat.getDrawable(
|
ContextCompat.getDrawable(
|
||||||
context,
|
context, R.drawable.ic_baseline_hearing_24
|
||||||
R.drawable.ic_baseline_hearing_24
|
|
||||||
)?.apply {
|
)?.apply {
|
||||||
setTint(
|
setTint(
|
||||||
ContextCompat.getColor(
|
ContextCompat.getColor(
|
||||||
context,
|
context, R.color.textColor
|
||||||
R.color.textColor
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -281,8 +276,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
val view = convertView ?: LayoutInflater.from(context)
|
val view = convertView ?: LayoutInflater.from(context).inflate(layout, null)
|
||||||
.inflate(layout, null)
|
|
||||||
|
|
||||||
val item = getItem(position)
|
val item = getItem(position)
|
||||||
|
|
||||||
|
@ -337,13 +331,12 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
dialog.search_loading_bar?.show()
|
dialog.search_loading_bar?.show()
|
||||||
ioSafe {
|
ioSafe {
|
||||||
val search = AbstractSubtitleEntities.SubtitleSearch(
|
val search =
|
||||||
query = query ?: return@ioSafe,
|
AbstractSubtitleEntities.SubtitleSearch(query = query ?: return@ioSafe,
|
||||||
imdb = imdbId,
|
imdb = imdbId,
|
||||||
epNumber = currentTempMeta.episode,
|
epNumber = currentTempMeta.episode,
|
||||||
seasonNumber = currentTempMeta.season,
|
seasonNumber = currentTempMeta.season,
|
||||||
lang = currentLanguageTwoLetters.ifBlank { null }
|
lang = currentLanguageTwoLetters.ifBlank { null })
|
||||||
)
|
|
||||||
val results = providers.amap {
|
val results = providers.amap {
|
||||||
try {
|
try {
|
||||||
it.search(search)
|
it.search(search)
|
||||||
|
@ -379,14 +372,12 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
dialog.search_filter.setOnClickListener { view ->
|
dialog.search_filter.setOnClickListener { view ->
|
||||||
val lang639_1 = languages.map { it.ISO_639_1 }
|
val lang639_1 = languages.map { it.ISO_639_1 }
|
||||||
activity?.showDialog(
|
activity?.showDialog(languages.map { it.languageName },
|
||||||
languages.map { it.languageName },
|
|
||||||
lang639_1.indexOf(currentLanguageTwoLetters),
|
lang639_1.indexOf(currentLanguageTwoLetters),
|
||||||
view?.context?.getString(R.string.subs_subtitle_languages)
|
view?.context?.getString(R.string.subs_subtitle_languages)
|
||||||
?: return@setOnClickListener,
|
?: return@setOnClickListener,
|
||||||
true,
|
true,
|
||||||
{ }
|
{ }) { index ->
|
||||||
) { index ->
|
|
||||||
currentLanguageTwoLetters = lang639_1[index]
|
currentLanguageTwoLetters = lang639_1[index]
|
||||||
dialog.subtitles_search.setQuery(dialog.subtitles_search.query, true)
|
dialog.subtitles_search.setQuery(dialog.subtitles_search.query, true)
|
||||||
}
|
}
|
||||||
|
@ -472,8 +463,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
if (uri == null) return@normalSafeApiCall
|
if (uri == null) return@normalSafeApiCall
|
||||||
val ctx = context ?: AcraApplication.context ?: return@normalSafeApiCall
|
val ctx = context ?: AcraApplication.context ?: return@normalSafeApiCall
|
||||||
// RW perms for the path
|
// RW perms for the path
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
val flags =
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
|
||||||
ctx.contentResolver.takePersistableUriPermission(uri, flags)
|
ctx.contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
|
|
||||||
|
@ -536,11 +527,9 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subsProvidersIsActive) {
|
if (subsProvidersIsActive) {
|
||||||
val loadFromOpenSubsFooter: TextView =
|
val loadFromOpenSubsFooter: TextView = layoutInflater.inflate(
|
||||||
layoutInflater.inflate(
|
R.layout.sort_bottom_footer_add_choice, null
|
||||||
R.layout.sort_bottom_footer_add_choice,
|
) as TextView
|
||||||
null
|
|
||||||
) as TextView
|
|
||||||
|
|
||||||
loadFromOpenSubsFooter.text =
|
loadFromOpenSubsFooter.text =
|
||||||
ctx.getString(R.string.player_load_subtitles_online)
|
ctx.getString(R.string.player_load_subtitles_online)
|
||||||
|
@ -592,8 +581,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
val subtitleIndexStart = currentSubtitles.indexOf(currentSelectedSubtitles) + 1
|
val subtitleIndexStart = currentSubtitles.indexOf(currentSelectedSubtitles) + 1
|
||||||
var subtitleIndex = subtitleIndexStart
|
var subtitleIndex = subtitleIndexStart
|
||||||
|
|
||||||
val subsArrayAdapter =
|
val subsArrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||||
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
|
||||||
subsArrayAdapter.add(ctx.getString(R.string.no_subtitles))
|
subsArrayAdapter.add(ctx.getString(R.string.no_subtitles))
|
||||||
subsArrayAdapter.addAll(currentSubtitles.map { it.name })
|
subsArrayAdapter.addAll(currentSubtitles.map { it.name })
|
||||||
|
|
||||||
|
@ -631,8 +619,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
val prefValues = ctx.resources.getStringArray(R.array.subtitles_encoding_values)
|
val prefValues = ctx.resources.getStringArray(R.array.subtitles_encoding_values)
|
||||||
|
|
||||||
val value = settingsManager.getString(
|
val value = settingsManager.getString(
|
||||||
ctx.getString(R.string.subtitles_encoding_key),
|
ctx.getString(R.string.subtitles_encoding_key), null
|
||||||
null
|
|
||||||
)
|
)
|
||||||
val index = prefValues.indexOf(value)
|
val index = prefValues.indexOf(value)
|
||||||
text = prefNames[if (index == -1) 0 else index]
|
text = prefNames[if (index == -1) 0 else index]
|
||||||
|
@ -644,28 +631,22 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
val prefNames = ctx.resources.getStringArray(R.array.subtitles_encoding_list)
|
val prefNames = ctx.resources.getStringArray(R.array.subtitles_encoding_list)
|
||||||
val prefValues = ctx.resources.getStringArray(R.array.subtitles_encoding_values)
|
val prefValues = ctx.resources.getStringArray(R.array.subtitles_encoding_values)
|
||||||
|
|
||||||
val currentPrefMedia =
|
val currentPrefMedia = settingsManager.getString(
|
||||||
settingsManager.getString(
|
ctx.getString(R.string.subtitles_encoding_key), null
|
||||||
ctx.getString(R.string.subtitles_encoding_key),
|
)
|
||||||
null
|
|
||||||
)
|
|
||||||
|
|
||||||
shouldDismiss = false
|
shouldDismiss = false
|
||||||
sourceDialog.dismissSafe(activity)
|
sourceDialog.dismissSafe(activity)
|
||||||
|
|
||||||
val index = prefValues.indexOf(currentPrefMedia)
|
val index = prefValues.indexOf(currentPrefMedia)
|
||||||
activity?.showDialog(
|
activity?.showDialog(prefNames.toList(),
|
||||||
prefNames.toList(),
|
|
||||||
if (index == -1) 0 else index,
|
if (index == -1) 0 else index,
|
||||||
ctx.getString(R.string.subtitles_encoding),
|
ctx.getString(R.string.subtitles_encoding),
|
||||||
true,
|
true,
|
||||||
{}) {
|
{}) {
|
||||||
settingsManager.edit()
|
settingsManager.edit().putString(
|
||||||
.putString(
|
ctx.getString(R.string.subtitles_encoding_key), prefValues[it]
|
||||||
ctx.getString(R.string.subtitles_encoding_key),
|
).apply()
|
||||||
prefValues[it]
|
|
||||||
)
|
|
||||||
.apply()
|
|
||||||
|
|
||||||
updateForcedEncoding(ctx)
|
updateForcedEncoding(ctx)
|
||||||
dismiss()
|
dismiss()
|
||||||
|
@ -944,17 +925,14 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
if (settingsManager.getBoolean(
|
if (settingsManager.getBoolean(
|
||||||
ctx.getString(R.string.episode_sync_enabled_key),
|
ctx.getString(R.string.episode_sync_enabled_key), true
|
||||||
true
|
|
||||||
)
|
)
|
||||||
)
|
) maxEpisodeSet = meta.episode
|
||||||
maxEpisodeSet = meta.episode
|
|
||||||
sync.modifyMaxEpisode(meta.episode)
|
sync.modifyMaxEpisode(meta.episode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta.tvType.isAnimeOp())
|
if (meta.tvType.isAnimeOp()) isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE
|
||||||
isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player_skip_op?.isVisible = isOpVisible
|
player_skip_op?.isVisible = isOpVisible
|
||||||
|
@ -966,9 +944,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAutoSelectSubtitle(
|
private fun getAutoSelectSubtitle(
|
||||||
subtitles: Set<SubtitleData>,
|
subtitles: Set<SubtitleData>, settings: Boolean, downloads: Boolean
|
||||||
settings: Boolean,
|
|
||||||
downloads: Boolean
|
|
||||||
): SubtitleData? {
|
): SubtitleData? {
|
||||||
val langCode = preferredAutoSelectSubtitles ?: return null
|
val langCode = preferredAutoSelectSubtitles ?: return null
|
||||||
val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null
|
val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null
|
||||||
|
@ -1014,23 +990,20 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
player.handleEvent(CSPlayerEvent.Play)
|
player.handleEvent(CSPlayerEvent.Play)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else
|
} else if (!langCode.isNullOrEmpty()) {
|
||||||
if (!langCode.isNullOrEmpty()) {
|
getAutoSelectSubtitle(
|
||||||
getAutoSelectSubtitle(
|
currentSubs, settings = true, downloads = false
|
||||||
currentSubs,
|
)?.let { sub ->
|
||||||
settings = true,
|
|
||||||
downloads = false
|
|
||||||
)?.let { sub ->
|
|
||||||
|
|
||||||
if (setSubtitles(sub)) {
|
|
||||||
player.saveData()
|
|
||||||
player.reloadPlayer(ctx)
|
|
||||||
player.handleEvent(CSPlayerEvent.Play)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (setSubtitles(sub)) {
|
||||||
|
player.saveData()
|
||||||
|
player.reloadPlayer(ctx)
|
||||||
|
player.handleEvent(CSPlayerEvent.Play)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1086,17 +1059,17 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
//Generate video title
|
//Generate video title
|
||||||
val playerVideoTitle = if (headerName != null) {
|
val playerVideoTitle = if (headerName != null) {
|
||||||
(headerName +
|
(headerName + if (tvType.isEpisodeBased() && episode != null) if (season == null) " - ${
|
||||||
if (tvType.isEpisodeBased() && episode != null)
|
ctx.getString(
|
||||||
if (season == null)
|
R.string.episode
|
||||||
" - ${ctx.getString(R.string.episode)} $episode"
|
)
|
||||||
else
|
} $episode"
|
||||||
" \"${ctx.getString(R.string.season_short)}${season}:${
|
else " \"${ctx.getString(R.string.season_short)}${season}:${
|
||||||
ctx.getString(
|
ctx.getString(
|
||||||
R.string.episode_short
|
R.string.episode_short
|
||||||
)
|
)
|
||||||
}${episode}\""
|
}${episode}\""
|
||||||
else "") + if (subName.isNullOrBlank() || subName == headerName) "" else " - $subName"
|
else "") + if (subName.isNullOrBlank() || subName == headerName) "" else " - $subName"
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
@ -1136,8 +1109,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name
|
val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name ?: "NULL"
|
||||||
?: "NULL"
|
|
||||||
|
|
||||||
player_video_title_rez?.text = when (titleRez) {
|
player_video_title_rez?.text = when (titleRez) {
|
||||||
0 -> ""
|
0 -> ""
|
||||||
|
@ -1160,14 +1132,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
): View? {
|
||||||
// this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason
|
// this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason
|
||||||
isTv = isTvSettings()
|
isTv = isTvSettings()
|
||||||
layout =
|
layout = if (isTv) R.layout.fragment_player_tv else R.layout.fragment_player
|
||||||
if (isTv) R.layout.fragment_player_tv else R.layout.fragment_player
|
|
||||||
|
|
||||||
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
|
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
|
||||||
sync = ViewModelProvider(this)[SyncViewModel::class.java]
|
sync = ViewModelProvider(this)[SyncViewModel::class.java]
|
||||||
|
@ -1179,8 +1148,57 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
return super.onCreateView(inflater, container, savedInstanceState)
|
return super.onCreateView(inflater, container, savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var timestampShowState = false
|
||||||
|
|
||||||
|
private fun displayTimeStamp(show: Boolean) {
|
||||||
|
if(timestampShowState == show) return
|
||||||
|
timestampShowState = show
|
||||||
|
skip_chapter_button?.apply {
|
||||||
|
val showWidth = 190.toPx
|
||||||
|
val noShowWidth = 10.toPx
|
||||||
|
//if((show && width == showWidth) || (!show && width == noShowWidth)) {
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
val to = if (show) showWidth else noShowWidth
|
||||||
|
val from = if (!show) showWidth else noShowWidth
|
||||||
|
|
||||||
|
isVisible = true
|
||||||
|
|
||||||
|
// just in case
|
||||||
|
val lay = layoutParams
|
||||||
|
lay.width = from
|
||||||
|
layoutParams = lay
|
||||||
|
|
||||||
|
ValueAnimator.ofInt(
|
||||||
|
from, to
|
||||||
|
).apply {
|
||||||
|
addListener(onEnd = {
|
||||||
|
if (!show) skip_chapter_button?.isVisible = false
|
||||||
|
})
|
||||||
|
addUpdateListener { valueAnimator ->
|
||||||
|
val value = valueAnimator.animatedValue as Int
|
||||||
|
val layoutParams: ViewGroup.LayoutParams = layoutParams
|
||||||
|
layoutParams.width = value
|
||||||
|
setLayoutParams(layoutParams)
|
||||||
|
}
|
||||||
|
duration = 500
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTimestampSkipped(timestamp: EpisodeSkip.SkipStamp) {
|
||||||
|
println("onTimestampSkipped:::$timestamp")
|
||||||
|
displayTimeStamp(false)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTimestamp(timestamp: EpisodeSkip.SkipStamp) {
|
override fun onTimestamp(timestamp: EpisodeSkip.SkipStamp) {
|
||||||
|
println("onTimestamp:::$timestamp")
|
||||||
skip_chapter_button.setText(timestamp.uiText)
|
skip_chapter_button.setText(timestamp.uiText)
|
||||||
|
displayTimeStamp(true)
|
||||||
|
skip_chapter_button?.handler?.postDelayed({
|
||||||
|
displayTimeStamp(false)
|
||||||
|
}, 6000)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -1198,8 +1216,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
settingsManager.getBoolean(getString(R.string.filter_sub_lang_key), false)
|
settingsManager.getBoolean(getString(R.string.filter_sub_lang_key), false)
|
||||||
if (filterSubByLang) {
|
if (filterSubByLang) {
|
||||||
val langFromPrefMedia = settingsManager.getStringSet(
|
val langFromPrefMedia = settingsManager.getStringSet(
|
||||||
this.getString(R.string.provider_lang_key),
|
this.getString(R.string.provider_lang_key), mutableSetOf("en")
|
||||||
mutableSetOf("en")
|
|
||||||
)
|
)
|
||||||
langFilterList = langFromPrefMedia?.mapNotNull {
|
langFilterList = langFromPrefMedia?.mapNotNull {
|
||||||
fromTwoLettersToLanguage(it)?.lowercase() ?: return@mapNotNull null
|
fromTwoLettersToLanguage(it)?.lowercase() ?: return@mapNotNull null
|
||||||
|
|
|
@ -127,6 +127,7 @@ interface IPlayer {
|
||||||
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null, // callback from player to give all embedded subtitles
|
embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null, // callback from player to give all embedded subtitles
|
||||||
onTracksInfoChanged: (() -> Unit)? = null, // Callback when tracks are changed, used for UI changes
|
onTracksInfoChanged: (() -> Unit)? = null, // Callback when tracks are changed, used for UI changes
|
||||||
onTimestampInvoked: ((EpisodeSkip.SkipStamp) -> Unit)? = null, // Callback when timestamps appear
|
onTimestampInvoked: ((EpisodeSkip.SkipStamp) -> Unit)? = null, // Callback when timestamps appear
|
||||||
|
onTimestampSkipped: ((EpisodeSkip.SkipStamp) -> Unit)? = null, // callback for when a chapter is skipped, aka when event is handled (or for future use when skip automatically ads/sponsor)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun releaseCallbacks()
|
fun releaseCallbacks()
|
||||||
|
|
|
@ -128,7 +128,14 @@ class PlayerGeneratorViewModel : ViewModel() {
|
||||||
val page = (generator as? RepoLinkGenerator?)?.page
|
val page = (generator as? RepoLinkGenerator?)?.page
|
||||||
if (page != null && meta is ResultEpisode) {
|
if (page != null && meta is ResultEpisode) {
|
||||||
_currentStamps.postValue(listOf())
|
_currentStamps.postValue(listOf())
|
||||||
_currentStamps.postValue(EpisodeSkip.getStamps(page, meta, duration))
|
_currentStamps.postValue(
|
||||||
|
EpisodeSkip.getStamps(
|
||||||
|
page,
|
||||||
|
meta,
|
||||||
|
duration,
|
||||||
|
hasNextEpisode() ?: false
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,43 +10,67 @@ import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
|
|
||||||
object EpisodeSkip {
|
object EpisodeSkip {
|
||||||
|
private const val TAG = "EpisodeSkip"
|
||||||
|
|
||||||
|
enum class SkipType(@StringRes name: Int) {
|
||||||
|
Opening(R.string.skip_type_op),
|
||||||
|
Ending(R.string.skip_type_ed),
|
||||||
|
Recap(R.string.skip_type_recap),
|
||||||
|
MixedOpening(R.string.skip_type_mixed_op),
|
||||||
|
MixedEnding(R.string.skip_type_mixed_ed),
|
||||||
|
Credits(R.string.skip_type_creddits),
|
||||||
|
Intro(R.string.skip_type_creddits),
|
||||||
|
}
|
||||||
|
|
||||||
data class SkipStamp(
|
data class SkipStamp(
|
||||||
@StringRes
|
val type: SkipType,
|
||||||
private val name: Int,
|
val skipToNextEpisode: Boolean,
|
||||||
val startMs: Long,
|
val startMs: Long,
|
||||||
val endMs: Long,
|
val endMs: Long,
|
||||||
) {
|
) {
|
||||||
val uiText = txt(R.string.skip_type_format, txt(name))
|
val uiText = if (skipToNextEpisode) txt(R.string.next_episode) else txt(
|
||||||
|
R.string.skip_type_format,
|
||||||
|
txt(type.name)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val cachedStamps = HashMap<Int, List<SkipStamp>>()
|
private val cachedStamps = HashMap<Int, List<SkipStamp>>()
|
||||||
|
|
||||||
|
private fun shouldSkipToNextEpisode(endMs: Long, episodeDurationMs: Long): Boolean {
|
||||||
|
return (episodeDurationMs - endMs) < 12_000L
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getStamps(
|
suspend fun getStamps(
|
||||||
data: LoadResponse,
|
data: LoadResponse,
|
||||||
episode: ResultEpisode,
|
episode: ResultEpisode,
|
||||||
episodeDurationMs: Long
|
episodeDurationMs: Long,
|
||||||
|
hasNextEpisode : Boolean,
|
||||||
): List<SkipStamp> {
|
): List<SkipStamp> {
|
||||||
cachedStamps[episode.id]?.let { list ->
|
cachedStamps[episode.id]?.let { list ->
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
val out = mutableListOf<SkipStamp>()
|
val out = mutableListOf<SkipStamp>()
|
||||||
println("CALLING WITH : ${data.syncData} $episode $episodeDurationMs")
|
Log.i(TAG, "Requesting SkipStamp from ${data.syncData}")
|
||||||
|
|
||||||
if (data is AnimeLoadResponse && (data.type == TvType.Anime || data.type == TvType.OVA)) {
|
if (data is AnimeLoadResponse && (data.type == TvType.Anime || data.type == TvType.OVA)) {
|
||||||
data.getMalId()?.toIntOrNull()?.let { malId ->
|
data.getMalId()?.toIntOrNull()?.let { malId ->
|
||||||
AniSkip.getResult(malId, episode.episode, episodeDurationMs)?.mapNotNull { stamp ->
|
AniSkip.getResult(malId, episode.episode, episodeDurationMs)?.mapNotNull { stamp ->
|
||||||
val name = when (stamp.skipType) {
|
val skipType = when (stamp.skipType) {
|
||||||
"op" -> R.string.skip_type_op
|
"op" -> SkipType.Opening
|
||||||
"ed" -> R.string.skip_type_ed
|
"ed" -> SkipType.Ending
|
||||||
"recap" -> R.string.skip_type_recap
|
"recap" -> SkipType.Recap
|
||||||
"mixed-ed" -> R.string.skip_type_mixed_ed
|
"mixed-ed" -> SkipType.MixedEnding
|
||||||
"mixed-op" -> R.string.skip_type_mixed_op
|
"mixed-op" -> SkipType.MixedOpening
|
||||||
else -> null
|
else -> null
|
||||||
} ?: return@mapNotNull null
|
} ?: return@mapNotNull null
|
||||||
|
val end = (stamp.interval.endTime * 1000.0).toLong()
|
||||||
|
val start = (stamp.interval.startTime * 1000.0).toLong()
|
||||||
SkipStamp(
|
SkipStamp(
|
||||||
name,
|
type = skipType,
|
||||||
(stamp.interval.startTime * 1000.0).toLong(),
|
skipToNextEpisode = hasNextEpisode && shouldSkipToNextEpisode(end, episodeDurationMs),
|
||||||
(stamp.interval.endTime * 1000.0).toLong()
|
startMs = start,
|
||||||
|
endMs = end
|
||||||
)
|
)
|
||||||
}?.let { list ->
|
}?.let { list ->
|
||||||
out.addAll(list)
|
out.addAll(list)
|
||||||
|
@ -62,25 +86,27 @@ object EpisodeSkip {
|
||||||
// taken from https://github.com/saikou-app/saikou/blob/3803f8a7a59b826ca193664d46af3a22bbc989f7/app/src/main/java/ani/saikou/others/AniSkip.kt
|
// taken from https://github.com/saikou-app/saikou/blob/3803f8a7a59b826ca193664d46af3a22bbc989f7/app/src/main/java/ani/saikou/others/AniSkip.kt
|
||||||
// the following is GPLv3 code https://github.com/saikou-app/saikou/blob/main/LICENSE.md
|
// the following is GPLv3 code https://github.com/saikou-app/saikou/blob/main/LICENSE.md
|
||||||
object AniSkip {
|
object AniSkip {
|
||||||
|
private const val TAG = "AniSkip"
|
||||||
suspend fun getResult(malId: Int, episodeNumber: Int, episodeLength: Long): List<Stamp>? {
|
suspend fun getResult(malId: Int, episodeNumber: Int, episodeLength: Long): List<Stamp>? {
|
||||||
return try {
|
return try {
|
||||||
val url =
|
val url =
|
||||||
"https://api.aniskip.com/v2/skip-times/$malId/$episodeNumber?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=${episodeLength / 1000L}"
|
"https://api.aniskip.com/v2/skip-times/$malId/$episodeNumber?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=${episodeLength / 1000L}"
|
||||||
println("URLLLL::::$url")
|
Log.i(TAG, "Requesting $url")
|
||||||
|
|
||||||
val a = app.get(url)
|
val a = app.get(url)
|
||||||
println("GOT RESPONSE:::.")
|
//println("GOT RESPONSE:::.")
|
||||||
val res = a.parsed<AniSkipResponse>()
|
val res = a.parsed<AniSkipResponse>()
|
||||||
Log.i("AniSkip", "Response = $res")
|
Log.i(TAG, "Found ${res.found} with ${res.results?.size} results")
|
||||||
|
|
||||||
|
// Log.i("AniSkip", "Response = $res")
|
||||||
if (res.found) res.results else null
|
if (res.found) res.results else null
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Log.i("AniSkip", "error = ${t.message}")
|
Log.i(TAG, "error = ${t.message}")
|
||||||
logError(t)
|
logError(t)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class AniSkipResponse(
|
data class AniSkipResponse(
|
||||||
@JsonSerialize val found: Boolean,
|
@JsonSerialize val found: Boolean,
|
||||||
@JsonSerialize val results: List<Stamp>?,
|
@JsonSerialize val results: List<Stamp>?,
|
||||||
|
@ -95,18 +121,6 @@ object AniSkip {
|
||||||
@JsonSerialize val episodeLength: Double
|
@JsonSerialize val episodeLength: Double
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
//fun String.getType(): String {
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
data class AniSkipInterval(
|
data class AniSkipInterval(
|
||||||
@JsonSerialize val startTime: Double,
|
@JsonSerialize val startTime: Double,
|
||||||
@JsonSerialize val endTime: Double
|
@JsonSerialize val endTime: Double
|
||||||
|
|
|
@ -321,9 +321,11 @@
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/skip_chapter_button"
|
android:id="@+id/skip_chapter_button"
|
||||||
style="@style/NiceButton"
|
style="@style/NiceButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="150dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="40dp"
|
||||||
android:layout_marginEnd="100dp"
|
android:layout_marginEnd="100dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:maxLines="1"
|
||||||
android:backgroundTint="@color/skipOpTransparent"
|
android:backgroundTint="@color/skipOpTransparent"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
|
|
||||||
<color name="black_overlay">#66000000</color>
|
<color name="black_overlay">#66000000</color>
|
||||||
<color name="darkBarTransparent">#C0121212</color>
|
<color name="darkBarTransparent">#C0121212</color>
|
||||||
<color name="skipOpTransparent">#C0121212</color>
|
<color name="skipOpTransparent">#4D121212</color>
|
||||||
<color name="darkBar">#121212</color>
|
<color name="darkBar">#121212</color>
|
||||||
<color name="videoProgress">#66B5B5B5</color> <!--66B5B5B5-->
|
<color name="videoProgress">#66B5B5B5</color> <!--66B5B5B5-->
|
||||||
<!--<color name="videoCache">#663D50FA</color>--> <!--66B5B5B5-->
|
<!--<color name="videoCache">#663D50FA</color>--> <!--66B5B5B5-->
|
||||||
|
|
Loading…
Reference in a new issue