This commit is contained in:
reduplicated 2022-11-04 18:55:03 +01:00
parent 7619b7e9d9
commit f77fe0a31e
9 changed files with 215 additions and 135 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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