Merge pull request #1420 from Laskco/skip-intro-merge-providers

fix(player): merge skip-intro providers instead of first-match-wins
This commit is contained in:
Nayif 2026-06-26 23:08:44 +05:30 committed by GitHub
commit ade6935330
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -25,30 +25,14 @@ object SkipIntroRepository {
val cacheKey = "$imdbId:$season:$episode"
cache[cacheKey]?.let { return it }
if (introDbConfigured) {
val result = fetchFromIntroDb(imdbId, season, episode)
if (result.isNotEmpty()) return result.also { cache[cacheKey] = it }
}
val introDb = if (introDbConfigured) fetchFromIntroDb(imdbId, season, episode) else emptyList()
val entries = resolveImdbEntries(imdbId)
val animeSkip = fetchAnimeSkipForEntries(entries, season, episode)
val malId = entries.getOrNull(season - 1)?.myanimelist?.toString()
?: entries.firstOrNull()?.myanimelist?.toString()
if (malId != null) {
val result = fetchFromAniSkip(malId, episode)
if (result.isNotEmpty()) return result.also { cache[cacheKey] = it }
}
val aniSkip = if (malId != null) fetchFromAniSkip(malId, episode) else emptyList()
val seasonAnilistId = entries.getOrNull(season - 1)?.anilist?.toString()
val fallbackAnilistId = entries.firstOrNull()?.anilist?.toString()
for ((anilistId, seasonFilter) in listOfNotNull(
seasonAnilistId?.let { it to null },
if (fallbackAnilistId != null && fallbackAnilistId != seasonAnilistId) fallbackAnilistId to season else null
)) {
val result = fetchFromAnimeSkip(anilistId, episode, season = seasonFilter)
if (result.isNotEmpty()) return result.also { cache[cacheKey] = it }
}
return emptyList<SkipInterval>().also { cache[cacheKey] = it }
return mergeByPriority(introDb, animeSkip, aniSkip).also { cache[cacheKey] = it }
}
suspend fun getSkipIntervalsForMal(
@ -62,41 +46,27 @@ object SkipIntroRepository {
val cacheKey = "mal:$malId:$episode"
cache[cacheKey]?.let { return it }
val aniSkipResult = fetchFromAniSkip(malId, episode)
if (aniSkipResult.isNotEmpty()) return aniSkipResult.also { cache[cacheKey] = it }
val aniSkip = fetchFromAniSkip(malId, episode)
val imdbId = try {
SkipIntroApi.resolveMalToImdb(malId)?.imdb
} catch (_: Exception) { null }
var introDb = emptyList<SkipInterval>()
var animeSkip = emptyList<SkipInterval>()
if (imdbId != null) {
val entries = resolveImdbEntries(imdbId)
val season = entries.indexOfFirst { it.myanimelist == malId.toIntOrNull() } + 1
if (introDbConfigured) {
val result = fetchFromIntroDb(imdbId, season, episode)
if (result.isNotEmpty()) return result.also { cache[cacheKey] = it }
}
val seasonAnilistId = entries.getOrNull(season - 1)?.anilist?.toString()
val fallbackAnilistId = entries.firstOrNull()?.anilist?.toString()
for ((anilistId, seasonFilter) in listOfNotNull(
seasonAnilistId?.let { it to null },
if (fallbackAnilistId != null && fallbackAnilistId != seasonAnilistId) fallbackAnilistId to season else null
)) {
val result = fetchFromAnimeSkip(anilistId, episode, season = seasonFilter)
if (result.isNotEmpty()) return result.also { cache[cacheKey] = it }
}
if (introDbConfigured) introDb = fetchFromIntroDb(imdbId, season, episode)
animeSkip = fetchAnimeSkipForEntries(entries, season, episode)
} else {
val anilistId = try {
SkipIntroApi.resolveMalToAnilist(malId)?.anilist?.toString()
} catch (_: Exception) { null }
if (anilistId != null) {
val result = fetchFromAnimeSkip(anilistId, episode, season = null)
if (result.isNotEmpty()) return result.also { cache[cacheKey] = it }
}
if (anilistId != null) animeSkip = fetchFromAnimeSkip(anilistId, episode, season = null)
}
return emptyList<SkipInterval>().also { cache[cacheKey] = it }
return mergeByPriority(introDb, animeSkip, aniSkip).also { cache[cacheKey] = it }
}
suspend fun getSkipIntervalsForKitsu(
@ -113,44 +83,69 @@ object SkipIntroRepository {
val malId = try {
SkipIntroApi.resolveKitsuToMal(kitsuId)?.myanimelist?.toString()
} catch (_: Exception) { null }
if (malId != null) {
val result = fetchFromAniSkip(malId, episode)
if (result.isNotEmpty()) return result.also { cache[cacheKey] = it }
}
val aniSkip = if (malId != null) fetchFromAniSkip(malId, episode) else emptyList()
val imdbId = try {
SkipIntroApi.resolveKitsuToImdb(kitsuId)?.imdb
} catch (_: Exception) { null }
var introDb = emptyList<SkipInterval>()
var animeSkip = emptyList<SkipInterval>()
if (imdbId != null) {
val entries = resolveImdbEntries(imdbId)
val season = entries.indexOfFirst { it.kitsu == kitsuId.toIntOrNull() } + 1
if (introDbConfigured) {
val result = fetchFromIntroDb(imdbId, season, episode)
if (result.isNotEmpty()) return result.also { cache[cacheKey] = it }
}
val seasonAnilistId = entries.getOrNull(season - 1)?.anilist?.toString()
val fallbackAnilistId = entries.firstOrNull()?.anilist?.toString()
for ((anilistId, seasonFilter) in listOfNotNull(
seasonAnilistId?.let { it to null },
if (fallbackAnilistId != null && fallbackAnilistId != seasonAnilistId) fallbackAnilistId to season else null
)) {
val result = fetchFromAnimeSkip(anilistId, episode, season = seasonFilter)
if (result.isNotEmpty()) return result.also { cache[cacheKey] = it }
}
if (introDbConfigured) introDb = fetchFromIntroDb(imdbId, season, episode)
animeSkip = fetchAnimeSkipForEntries(entries, season, episode)
} else {
val anilistId = try {
SkipIntroApi.resolveKitsuToAnilist(kitsuId)?.anilist?.toString()
} catch (_: Exception) { null }
if (anilistId != null) {
val result = fetchFromAnimeSkip(anilistId, episode, season = null)
if (result.isNotEmpty()) return result.also { cache[cacheKey] = it }
}
if (anilistId != null) animeSkip = fetchFromAnimeSkip(anilistId, episode, season = null)
}
return emptyList<SkipInterval>().also { cache[cacheKey] = it }
return mergeByPriority(introDb, animeSkip, aniSkip).also { cache[cacheKey] = it }
}
/**
* Merge provider results into one best-of: fill each segment category (opening / ending /
* recap) from the highest-priority provider that has it. Arguments MUST be passed in priority
* order (IntroDB has the broadest coverage, then Anime-Skip, then AniSkip), so a partial
* result from one provider never shadows a complete segment from another.
*/
private fun mergeByPriority(vararg providerResults: List<SkipInterval>): List<SkipInterval> {
val chosen = LinkedHashMap<String, SkipInterval>()
for (result in providerResults) {
for (interval in result) {
val category = segmentCategory(interval.type) ?: continue
if (category !in chosen) chosen[category] = interval
}
}
return chosen.values.toList()
}
private fun segmentCategory(type: String): String? = when (type.lowercase()) {
"intro", "op", "mixed-op" -> "opening"
"outro", "ed", "mixed-ed", "credits", "ending" -> "ending"
"recap" -> "recap"
else -> null
}
// AnimeSkip: season-specific AniList ID first, then season-1 as a season-filtered fallback.
private suspend fun fetchAnimeSkipForEntries(
entries: List<ArmEntry>,
season: Int,
episode: Int
): List<SkipInterval> {
val seasonAnilistId = entries.getOrNull(season - 1)?.anilist?.toString()
val fallbackAnilistId = entries.firstOrNull()?.anilist?.toString()
for ((anilistId, seasonFilter) in listOfNotNull(
seasonAnilistId?.let { it to null },
if (fallbackAnilistId != null && fallbackAnilistId != seasonAnilistId) fallbackAnilistId to season else null
)) {
val result = fetchFromAnimeSkip(anilistId, episode, season = seasonFilter)
if (result.isNotEmpty()) return result
}
return emptyList()
}
private suspend fun fetchFromIntroDb(imdbId: String, season: Int, episode: Int): List<SkipInterval> {