mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-29 01:29:02 +03:00
Initial support for AniSkip (#772)
Co-authored-by: jmir1 <jhmiramon@gmail.com>
This commit is contained in:
parent
51ae6f8b54
commit
5f8150c735
11 changed files with 393 additions and 2 deletions
|
@ -122,12 +122,14 @@ fun AnimeScreen(
|
|||
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
|
||||
onEditCategoryClicked: (() -> Unit)?,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
changeAnimeSkipIntro: (() -> Unit)?,
|
||||
|
||||
// For bottom action menu
|
||||
onMultiBookmarkClicked: (List<Episode>, bookmarked: Boolean) -> Unit,
|
||||
onMultiMarkAsSeenClicked: (List<Episode>, markAsSeen: Boolean) -> Unit,
|
||||
onMarkPreviousAsSeenClicked: (Episode) -> Unit,
|
||||
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
||||
|
||||
) {
|
||||
if (windowWidthSizeClass == WindowWidthSizeClass.Compact) {
|
||||
AnimeScreenSmallImpl(
|
||||
|
@ -149,6 +151,7 @@ fun AnimeScreen(
|
|||
onDownloadActionClicked = onDownloadActionClicked,
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
changeAnimeSkipIntro = changeAnimeSkipIntro,
|
||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||
onMultiMarkAsSeenClicked = onMultiMarkAsSeenClicked,
|
||||
onMarkPreviousAsSeenClicked = onMarkPreviousAsSeenClicked,
|
||||
|
@ -174,6 +177,7 @@ fun AnimeScreen(
|
|||
onShareClicked = onShareClicked,
|
||||
onDownloadActionClicked = onDownloadActionClicked,
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
changeAnimeSkipIntro = changeAnimeSkipIntro,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||
onMultiMarkAsSeenClicked = onMultiMarkAsSeenClicked,
|
||||
|
@ -207,12 +211,14 @@ private fun AnimeScreenSmallImpl(
|
|||
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
|
||||
onEditCategoryClicked: (() -> Unit)?,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
changeAnimeSkipIntro: (() -> Unit)?,
|
||||
|
||||
// For bottom action menu
|
||||
onMultiBookmarkClicked: (List<Episode>, bookmarked: Boolean) -> Unit,
|
||||
onMultiMarkAsSeenClicked: (List<Episode>, markAsRead: Boolean) -> Unit,
|
||||
onMarkPreviousAsSeenClicked: (Episode) -> Unit,
|
||||
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
||||
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||
|
@ -305,6 +311,7 @@ private fun AnimeScreenSmallImpl(
|
|||
onDownloadClicked = onDownloadActionClicked,
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
changeAnimeSkipIntro = changeAnimeSkipIntro,
|
||||
doGlobalSearch = onSearch,
|
||||
scrollBehavior = scrollBehavior,
|
||||
actionModeCounter = selected.size,
|
||||
|
@ -409,12 +416,14 @@ fun AnimeScreenLargeImpl(
|
|||
onDownloadActionClicked: ((DownloadAction) -> Unit)?,
|
||||
onEditCategoryClicked: (() -> Unit)?,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
changeAnimeSkipIntro: (() -> Unit)?,
|
||||
|
||||
// For bottom action menu
|
||||
onMultiBookmarkClicked: (List<Episode>, bookmarked: Boolean) -> Unit,
|
||||
onMultiMarkAsSeenClicked: (List<Episode>, markAsRead: Boolean) -> Unit,
|
||||
onMarkPreviousAsSeenClicked: (Episode) -> Unit,
|
||||
onMultiDeleteClicked: (List<Episode>) -> Unit,
|
||||
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val density = LocalDensity.current
|
||||
|
@ -466,6 +475,7 @@ fun AnimeScreenLargeImpl(
|
|||
onDownloadClicked = onDownloadActionClicked,
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
changeAnimeSkipIntro = changeAnimeSkipIntro,
|
||||
actionModeCounter = selected.size,
|
||||
onSelectAll = {
|
||||
selected.clear()
|
||||
|
|
|
@ -53,6 +53,7 @@ fun AnimeSmallAppBar(
|
|||
onShareClicked: (() -> Unit)?,
|
||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||
onEditCategoryClicked: (() -> Unit)?,
|
||||
changeAnimeSkipIntro: (() -> Unit)?,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
// For action mode
|
||||
actionModeCounter: Int,
|
||||
|
@ -197,6 +198,13 @@ fun AnimeSmallAppBar(
|
|||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(R.string.action_change_intro_length)) },
|
||||
onClick = {
|
||||
changeAnimeSkipIntro?.invoke()
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ fun AnimeTopAppBar(
|
|||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||
onEditCategoryClicked: (() -> Unit)?,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
changeAnimeSkipIntro: (() -> Unit)?,
|
||||
doGlobalSearch: (query: String, global: Boolean) -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior?,
|
||||
// For action mode
|
||||
|
@ -53,6 +54,7 @@ fun AnimeTopAppBar(
|
|||
onSelectAll: () -> Unit,
|
||||
onInvertSelection: () -> Unit,
|
||||
onSmallAppBarHeightChanged: (Int) -> Unit,
|
||||
|
||||
) {
|
||||
val scrollPercentageProvider = { scrollBehavior?.scrollFraction?.coerceIn(0f, 1f) ?: 0f }
|
||||
val inverseScrollPercentageProvider = { 1f - scrollPercentageProvider() }
|
||||
|
@ -108,9 +110,11 @@ fun AnimeTopAppBar(
|
|||
onDownloadClicked = onDownloadClicked,
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
changeAnimeSkipIntro = changeAnimeSkipIntro,
|
||||
actionModeCounter = actionModeCounter,
|
||||
onSelectAll = onSelectAll,
|
||||
onInvertSelection = onInvertSelection,
|
||||
|
||||
)
|
||||
},
|
||||
) { measurables, constraints ->
|
||||
|
|
|
@ -136,6 +136,11 @@ object PreferenceKeys {
|
|||
|
||||
const val autoClearChapterCache = "auto_clear_chapter_cache"
|
||||
|
||||
const val enableAniSkip = "pref_enable_ani_skip"
|
||||
const val enableAutoSkip_AniSkip = "pref_enable_auto_skip_ani_skip"
|
||||
const val waitingTimeAniSkip = "pref_waiting_time_aniskip"
|
||||
const val enableNetflixStyleAniSkip = "pref_enable_netflixStyle_aniskip"
|
||||
|
||||
fun trackUsername(syncId: Long) = "pref_mangasync_username_$syncId"
|
||||
|
||||
fun trackPassword(syncId: Long) = "pref_mangasync_password_$syncId"
|
||||
|
|
|
@ -461,4 +461,9 @@ class PreferencesHelper(val context: Context) {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun aniSkipEnabled() = prefs.getBoolean(Keys.enableAniSkip, false)
|
||||
fun autoSkipAniSkip() = prefs.getBoolean(Keys.enableAutoSkip_AniSkip, false)
|
||||
fun waitingTimeAniSkip() = prefs.getString(Keys.waitingTimeAniSkip, "5")
|
||||
fun enableNetflixStyleAniSkip() = prefs.getBoolean(Keys.enableNetflixStyleAniSkip, false)
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import eu.kanade.tachiyomi.data.download.AnimeDownloadService
|
|||
import eu.kanade.tachiyomi.data.download.model.AnimeDownload
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch
|
||||
import eu.kanade.tachiyomi.databinding.PrefSkipIntroLengthBinding
|
||||
import eu.kanade.tachiyomi.network.HttpException
|
||||
import eu.kanade.tachiyomi.ui.anime.episode.DownloadCustomEpisodesDialog
|
||||
import eu.kanade.tachiyomi.ui.anime.episode.EpisodesSettingsSheet
|
||||
|
@ -153,6 +154,7 @@ class AnimeController :
|
|||
onMultiMarkAsSeenClicked = presenter::markEpisodesSeen,
|
||||
onMarkPreviousAsSeenClicked = presenter::markPreviousEpisodeSeen,
|
||||
onMultiDeleteClicked = this::deleteEpisodesWithConfirmation,
|
||||
changeAnimeSkipIntro = this::changeAnimeSkipIntro.takeIf { successState.anime.favorite },
|
||||
)
|
||||
} else {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
|
@ -198,6 +200,31 @@ class AnimeController :
|
|||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun changeAnimeSkipIntro() {
|
||||
val anime = presenter.anime ?: return
|
||||
val playerActivity = PlayerActivity()
|
||||
var newSkipIntroLength = playerActivity.presenter.getAnimeSkipIntroLength()
|
||||
val binding = PrefSkipIntroLengthBinding.inflate(LayoutInflater.from(activity))
|
||||
|
||||
playerActivity.presenter.anime = anime.toDbAnime()
|
||||
with(binding.skipIntroColumn) {
|
||||
value = playerActivity.presenter.getAnimeSkipIntroLength()
|
||||
setOnValueChangedListener { _, _, newValue ->
|
||||
newSkipIntroLength = newValue
|
||||
}
|
||||
}
|
||||
activity?.let {
|
||||
MaterialAlertDialogBuilder(it)
|
||||
.setTitle(R.string.action_change_intro_length)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
playerActivity.presenter.setAnimeSkipIntroLength(newSkipIntroLength)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun shareAnime() {
|
||||
val context = view?.context ?: return
|
||||
val anime = presenter.anime ?: return
|
||||
|
|
|
@ -49,6 +49,9 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
|||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.databinding.PlayerActivityBinding
|
||||
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
||||
import eu.kanade.tachiyomi.util.AniSkipApi
|
||||
import eu.kanade.tachiyomi.util.SkipType
|
||||
import eu.kanade.tachiyomi.util.Stamp
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
|
@ -58,6 +61,7 @@ import eu.kanade.tachiyomi.util.system.toShareIntent
|
|||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import `is`.xyz.mpv.MPVLib
|
||||
import `is`.xyz.mpv.Utils
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import logcat.LogPriority
|
||||
import nucleus.factory.RequiresPresenter
|
||||
import java.io.File
|
||||
|
@ -1047,7 +1051,15 @@ class PlayerActivity :
|
|||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun skipIntro(view: View) {
|
||||
if (playerControls.binding.controlsSkipIntroBtn.text != "") {
|
||||
if (skipType != null) {
|
||||
// this stop the counter
|
||||
if (waitingAniSkip > 0) {
|
||||
waitingAniSkip = -1
|
||||
return
|
||||
}
|
||||
skipType.let { MPVLib.command(arrayOf("seek", "${aniSkipInterval!!.first{it.skipType == skipType}.interval.endTime}", "absolute")) }
|
||||
AniSkipApi.PlayerUtils(binding, aniSkipInterval!!).skipAnimation(skipType!!)
|
||||
} else if (playerControls.binding.controlsSkipIntroBtn.text != "") {
|
||||
doubleTapSeek(presenter.getAnimeSkipIntroLength(), isDoubleTap = false)
|
||||
playerControls.resetControlsFade()
|
||||
}
|
||||
|
@ -1437,6 +1449,7 @@ class PlayerActivity :
|
|||
.coerceAtLeast(0)
|
||||
}
|
||||
}
|
||||
|
||||
launchUI {
|
||||
showLoadingIndicator(false)
|
||||
if (preferences.adjustOrientationVideoDimensions()) {
|
||||
|
@ -1449,6 +1462,54 @@ class PlayerActivity :
|
|||
}
|
||||
}
|
||||
}
|
||||
// aniSkip stuff
|
||||
waitingAniSkip = preferences.waitingTimeAniSkip()!!.toInt()
|
||||
runBlocking {
|
||||
aniSkipInterval = presenter.aniSkipResponse()
|
||||
}
|
||||
}
|
||||
|
||||
private val aniSkipEnable = preferences.aniSkipEnabled()
|
||||
private val autoSkipAniSkip = preferences.autoSkipAniSkip()
|
||||
private val netflixStyle = preferences.enableNetflixStyleAniSkip()
|
||||
|
||||
private var aniSkipInterval: List<Stamp>? = null
|
||||
private var waitingAniSkip = preferences.waitingTimeAniSkip()!!.toInt()
|
||||
|
||||
var skipType: SkipType? = null
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun aniSkipStuff(value: Long) {
|
||||
if (aniSkipEnable) {
|
||||
// if it doesn't find the opening it will show the +85 button
|
||||
val showNormalSkipButton = aniSkipInterval?.firstOrNull { it.skipType == SkipType.op || it.skipType == SkipType.mixedOp } == null
|
||||
if (showNormalSkipButton) return
|
||||
|
||||
skipType = aniSkipInterval?.firstOrNull { it.interval.startTime <= value && it.interval.endTime > value }?.skipType
|
||||
skipType?.let { skipType ->
|
||||
val aniSkipPlayerUtils = AniSkipApi.PlayerUtils(binding, aniSkipInterval!!)
|
||||
if (netflixStyle) {
|
||||
// show a toast with the seconds before the skip
|
||||
if (waitingAniSkip == preferences.waitingTimeAniSkip()!!.toInt()) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
"AniSkip: ${getString(R.string.player_aniskip_dontskip_toast,waitingAniSkip)}",
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
aniSkipPlayerUtils.showSkipButton(skipType, waitingAniSkip)
|
||||
waitingAniSkip--
|
||||
} else if (autoSkipAniSkip) {
|
||||
skipType.let { MPVLib.command(arrayOf("seek", "${aniSkipInterval!!.first{it.skipType == skipType}.interval.endTime}", "absolute")) }
|
||||
} else {
|
||||
aniSkipPlayerUtils.showSkipButton(skipType)
|
||||
}
|
||||
} ?: run {
|
||||
launchUI {
|
||||
playerControls.binding.controlsSkipIntroBtn.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mpv events
|
||||
|
@ -1456,7 +1517,10 @@ class PlayerActivity :
|
|||
private fun eventPropertyUi(property: String, value: Long) {
|
||||
when (property) {
|
||||
"demuxer-cache-time" -> playerControls.updateBufferPosition(value.toInt())
|
||||
"time-pos" -> playerControls.updatePlaybackPos(value.toInt())
|
||||
"time-pos" -> {
|
||||
playerControls.updatePlaybackPos(value.toInt())
|
||||
aniSkipStuff(value)
|
||||
}
|
||||
"duration" -> playerControls.updatePlaybackDuration(value.toInt())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,10 +32,14 @@ import eu.kanade.tachiyomi.data.saver.Image
|
|||
import eu.kanade.tachiyomi.data.saver.ImageSaver
|
||||
import eu.kanade.tachiyomi.data.saver.Location
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
|
||||
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingUpdateJob
|
||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.ui.reader.SaveImageNotifier
|
||||
import eu.kanade.tachiyomi.util.AniSkipApi
|
||||
import eu.kanade.tachiyomi.util.Stamp
|
||||
import eu.kanade.tachiyomi.util.editCover
|
||||
import eu.kanade.tachiyomi.util.episode.getEpisodeSort
|
||||
import eu.kanade.tachiyomi.util.lang.byteSize
|
||||
|
@ -585,6 +589,35 @@ class PlayerPresenter(
|
|||
"${anime.title} - ${episode.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()),
|
||||
) + filenameSuffix
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response of the AniSkipApi for this episode.
|
||||
* just works if tracking is enabled.
|
||||
*/
|
||||
|
||||
suspend fun aniSkipResponse(): List<Stamp>? {
|
||||
val trackManager = Injekt.get<TrackManager>()
|
||||
var malId: Long?
|
||||
val episodeNumber = getCurrentEpisodeIndex() + 1
|
||||
if (getTracks.await(animeId).isEmpty()) {
|
||||
logcat { "AniSkip: No tracks found for anime $animeId" }
|
||||
return null
|
||||
}
|
||||
|
||||
getTracks.await(animeId).map { track ->
|
||||
val service = trackManager.getService(track.syncId)
|
||||
malId = when (service) {
|
||||
is MyAnimeList -> track.remoteId
|
||||
is Anilist -> AniSkipApi().getMalIdFromAL(track.remoteId)
|
||||
else -> null
|
||||
}
|
||||
val duration = view?.player?.duration ?: return null
|
||||
return malId?.let {
|
||||
AniSkipApi().getResult(it.toInt(), episodeNumber, duration.toLong())
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private const val MAX_FILE_NAME_BYTES = 250
|
||||
|
|
|
@ -144,6 +144,53 @@ class SettingsPlayerController : SettingsController() {
|
|||
}
|
||||
}
|
||||
|
||||
preferenceCategory {
|
||||
titleRes = R.string.pref_category_player_aniskip
|
||||
|
||||
switchPreference {
|
||||
key = Keys.enableAniSkip
|
||||
titleRes = R.string.pref_enable_aniskip
|
||||
defaultValue = false
|
||||
}
|
||||
|
||||
switchPreference {
|
||||
key = Keys.enableAutoSkip_AniSkip
|
||||
titleRes = R.string.pref_enable_auto_skip_ani_skip
|
||||
defaultValue = false
|
||||
}
|
||||
|
||||
switchPreference {
|
||||
key = Keys.enableNetflixStyleAniSkip
|
||||
titleRes = R.string.pref_enable_netflixStyle_aniskip
|
||||
defaultValue = true
|
||||
}
|
||||
|
||||
listPreference {
|
||||
key = Keys.waitingTimeAniSkip
|
||||
titleRes = R.string.pref_waiting_time_aniskip
|
||||
|
||||
entriesRes = arrayOf(
|
||||
R.string.pref_waiting_time_aniskip_5,
|
||||
R.string.pref_waiting_time_aniskip_6,
|
||||
R.string.pref_waiting_time_aniskip_7,
|
||||
R.string.pref_waiting_time_aniskip_8,
|
||||
R.string.pref_waiting_time_aniskip_9,
|
||||
R.string.pref_waiting_time_aniskip_10,
|
||||
)
|
||||
entryValues = arrayOf(
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"10",
|
||||
)
|
||||
defaultValue = "5"
|
||||
|
||||
summary = "%s"
|
||||
}
|
||||
}
|
||||
|
||||
preferenceCategory {
|
||||
titleRes = R.string.pref_category_internal_player
|
||||
|
||||
|
|
169
app/src/main/java/eu/kanade/tachiyomi/util/AniSkipApi.kt
Normal file
169
app/src/main/java/eu/kanade/tachiyomi/util/AniSkipApi.kt
Normal file
|
@ -0,0 +1,169 @@
|
|||
package eu.kanade.tachiyomi.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.PlayerActivityBinding
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.jsonMime
|
||||
import eu.kanade.tachiyomi.ui.player.PlayerActivity
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import `is`.xyz.mpv.MPVLib
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.*
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.*
|
||||
|
||||
class AniSkipApi {
|
||||
private val client = OkHttpClient()
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
// credits: https://github.com/saikou-app/saikou/blob/main/app/src/main/java/ani/saikou/others/AniSkip.kt
|
||||
fun getResult(malId: Int, episodeNumber: Int, episodeLength: Long): List<Stamp>? {
|
||||
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"
|
||||
return try {
|
||||
val a = client.newCall(GET(url)).execute().body!!.string()
|
||||
val res = json.decodeFromString<AniSkipResponse>(a)
|
||||
if (res.found) res.results else null
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getMalIdFromAL(id: Long): Long {
|
||||
val query = """
|
||||
query{
|
||||
Media(id:$id){idMal}
|
||||
}
|
||||
""".trimMargin()
|
||||
val response = client.newCall(
|
||||
POST(
|
||||
"https://graphql.anilist.co",
|
||||
body = buildJsonObject { put("query", query) }.toString().toRequestBody(jsonMime),
|
||||
),
|
||||
).execute()
|
||||
return response.body!!.string().substringAfter("idMal\":").substringBefore("}")
|
||||
.toLongOrNull() ?: 0
|
||||
}
|
||||
|
||||
class PlayerUtils(
|
||||
private val binding: PlayerActivityBinding,
|
||||
private val aniSkipResponse: List<Stamp>,
|
||||
) {
|
||||
private val playerControls get() = binding.playerControls
|
||||
private val activity: PlayerActivity get() = binding.root.context as PlayerActivity
|
||||
|
||||
fun showSkipButton(skipType: SkipType) {
|
||||
val skipButtonString = when (skipType) {
|
||||
SkipType.ed -> R.string.player_aniskip_ed
|
||||
SkipType.op -> R.string.player_aniskip_op
|
||||
SkipType.recap -> R.string.player_aniskip_recap
|
||||
SkipType.mixedOp -> R.string.player_aniskip_mixedOp
|
||||
}
|
||||
launchUI {
|
||||
playerControls.binding.controlsSkipIntroBtn.isVisible = true
|
||||
playerControls.binding.controlsSkipIntroBtn.text = activity.getString(skipButtonString)
|
||||
}
|
||||
}
|
||||
|
||||
// this is used when netflixStyle is enabled
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun showSkipButton(skipType: SkipType, waitingTime: Int) {
|
||||
val skipTime = when (skipType) {
|
||||
SkipType.ed -> aniSkipResponse.first { it.skipType == SkipType.ed }.interval
|
||||
SkipType.op -> aniSkipResponse.first { it.skipType == SkipType.op }.interval
|
||||
SkipType.recap -> aniSkipResponse.first { it.skipType == SkipType.recap }.interval
|
||||
SkipType.mixedOp -> aniSkipResponse.first { it.skipType == SkipType.mixedOp }.interval
|
||||
}
|
||||
if (waitingTime > -1) {
|
||||
if (waitingTime > 0) {
|
||||
launchUI {
|
||||
playerControls.binding.controlsSkipIntroBtn.isVisible = true
|
||||
playerControls.binding.controlsSkipIntroBtn.text = activity.getString(R.string.player_aniskip_dontskip)
|
||||
}
|
||||
} else {
|
||||
seekTo(skipTime.endTime)
|
||||
skipAnimation(skipType)
|
||||
}
|
||||
} else {
|
||||
// when waitingTime is -1, it means that the user cancelled the skip
|
||||
showSkipButton(skipType)
|
||||
}
|
||||
}
|
||||
|
||||
fun skipAnimation(skipType: SkipType) {
|
||||
binding.secondsView.binding.doubleTapSeconds.text = activity.getString(R.string.player_aniskip_skip, skipType.getString())
|
||||
|
||||
binding.secondsView.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
rightToRight = ConstraintLayout.LayoutParams.PARENT_ID
|
||||
leftToLeft = ConstraintLayout.LayoutParams.UNSET
|
||||
}
|
||||
binding.secondsView.isVisible = true
|
||||
binding.secondsView.isForward = true
|
||||
|
||||
binding.ffwdBg.visibility = View.VISIBLE
|
||||
binding.ffwdBg.animate().alpha(0.15f).setDuration(100).withEndAction {
|
||||
binding.secondsView.animate().alpha(1f).setDuration(500).withEndAction {
|
||||
binding.secondsView.animate().alpha(0f).setDuration(500).withEndAction {
|
||||
binding.ffwdBg.animate().alpha(0f).setDuration(100).withEndAction {
|
||||
binding.ffwdBg.visibility = View.GONE
|
||||
binding.secondsView.isVisible = false
|
||||
binding.secondsView.alpha = 1f
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun seekTo(time: Double) {
|
||||
MPVLib.command(arrayOf("seek", time.toString(), "absolute"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AniSkipResponse(
|
||||
val found: Boolean,
|
||||
val results: List<Stamp>?,
|
||||
val message: String?,
|
||||
val statusCode: Int,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Stamp(
|
||||
val interval: AniSkipInterval,
|
||||
val skipType: SkipType,
|
||||
val skipId: String,
|
||||
val episodeLength: Double,
|
||||
)
|
||||
|
||||
@Suppress("EnumEntryName")
|
||||
@Serializable
|
||||
enum class SkipType {
|
||||
op, ed, recap, @SerialName("mixed-op")
|
||||
mixedOp;
|
||||
|
||||
fun getString(): String {
|
||||
return when (this) {
|
||||
op -> "Opening"
|
||||
ed -> "Ending"
|
||||
recap -> "Recap"
|
||||
mixedOp -> "Mixed-op"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AniSkipInterval(
|
||||
val startTime: Double,
|
||||
val endTime: Double,
|
||||
)
|
|
@ -1068,6 +1068,25 @@
|
|||
<string name="playback_options_speed">Playback speed</string>
|
||||
<string name="playback_options_quality">Video quality</string>
|
||||
<string name="playback_options_title">Playback options</string>
|
||||
<string name="action_change_intro_length">Change intro length</string>
|
||||
<string name="player_aniskip_op">Skip Opening</string>
|
||||
<string name="player_aniskip_ed">Skip Ending</string>
|
||||
<string name="player_aniskip_mixedOp">Skip MixedOp</string>
|
||||
<string name="player_aniskip_recap">Skip Recap</string>
|
||||
<string name="pref_category_player_aniskip">AniSkip Settings</string>
|
||||
<string name="pref_enable_aniskip">Enable AniSkip</string>
|
||||
<string name="pref_enable_auto_skip_ani_skip">Enable auto skip</string>
|
||||
<string name="pref_waiting_time_aniskip">Button timeout</string>
|
||||
<string name="pref_waiting_time_aniskip_5">5 seconds</string>
|
||||
<string name="pref_waiting_time_aniskip_6">6 seconds</string>
|
||||
<string name="pref_waiting_time_aniskip_7">7 seconds</string>
|
||||
<string name="pref_waiting_time_aniskip_8">8 seconds</string>
|
||||
<string name="pref_waiting_time_aniskip_9">9 seconds</string>
|
||||
<string name="pref_waiting_time_aniskip_10">10 seconds</string>
|
||||
<string name="pref_enable_netflixStyle_aniskip">Enable Netflix style</string>
|
||||
<string name="player_aniskip_dontskip">Don\'t skip</string>
|
||||
<string name="player_aniskip_dontskip_toast">Skip in %d seconds</string>
|
||||
<string name="player_aniskip_skip">%s Skipped</string>
|
||||
|
||||
<string name="pref_navigate_pan">Navigate to pan</string>
|
||||
<string name="pref_landscape_zoom">Zoom landscape image</string>
|
||||
|
|
Loading…
Reference in a new issue