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

fix(player): speed up skip-segment launch prep
This commit is contained in:
Nayif 2026-06-27 16:44:53 +05:30 committed by GitHub
commit 5137cea233
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 98 additions and 55 deletions

View file

@ -2325,8 +2325,10 @@ private fun MainAppContent(
)
if (!forceInternal && (forceExternal || playerSettings.externalPlayerEnabled)) {
coroutineScope.launch { openExternalPlayback(playerLaunch) }
StreamsRepository.cancelLoading()
streamRouteScope.launch {
openExternalPlayback(playerLaunch)
StreamsRepository.cancelLoading()
}
return
}

View file

@ -6,6 +6,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonArray
@ -33,35 +34,47 @@ suspend fun prepareExternalPlayerLaunch(
preferredLanguage: String,
secondaryLanguage: String?,
onOverlayMessage: (String?) -> Unit,
): ExternalPlayerPlaybackRequest {
): ExternalPlayerPlaybackRequest = coroutineScope {
var result = request
if (forwardSubtitles && !preferredLanguage.equals(SubtitleLanguageOption.NONE, ignoreCase = true)) {
onOverlayMessage("Loading subtitles from addons...")
val subtitlesDeferred = if (forwardSubtitles && !preferredLanguage.equals(SubtitleLanguageOption.NONE, ignoreCase = true)) {
async {
onOverlayMessage("Loading subtitles from addons...")
val subtitles = SubtitleForwarder.fetchForExternalPlayer(
type = type,
videoId = videoId,
preferredLanguage = preferredLanguage,
secondaryLanguage = secondaryLanguage,
)
val subtitles = SubtitleForwarder.fetchForExternalPlayer(
type = type,
videoId = videoId,
preferredLanguage = preferredLanguage,
secondaryLanguage = secondaryLanguage,
)
if (subtitles != null) {
onOverlayMessage("Downloading subtitles...")
val cachedSubtitles = SubtitleCacheProvider.cacheForExternalPlayer(subtitles)
// Fallback: use original URLs if caching fails
result = result.copy(subtitles = cachedSubtitles ?: subtitles)
if (subtitles != null) {
onOverlayMessage("Downloading subtitles...")
val cachedSubtitles = SubtitleCacheProvider.cacheForExternalPlayer(subtitles)
// Fallback: use original URLs if caching fails
cachedSubtitles ?: subtitles
} else {
null
}
}
} else {
null
}
if (sendSkipSegments) {
val skipSegmentsJson = resolveSkipSegmentsJson(videoId, request.season, request.episode)
if (skipSegmentsJson != null) {
result = result.copy(skipSegmentsJson = skipSegmentsJson)
}
val skipSegmentsDeferred = if (sendSkipSegments) {
async { resolveSkipSegmentsJson(videoId, request.season, request.episode) }
} else {
null
}
return result
subtitlesDeferred?.await()?.let { subtitles ->
result = result.copy(subtitles = subtitles)
}
skipSegmentsDeferred?.await()?.let { skipSegmentsJson ->
result = result.copy(skipSegmentsJson = skipSegmentsJson)
}
return@coroutineScope result
}
/**

View file

@ -1,6 +1,8 @@
package com.nuvio.app.features.player.skip
import com.nuvio.app.features.player.PlayerSettingsRepository
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
object SkipIntroRepository {
@ -17,48 +19,64 @@ object SkipIntroRepository {
season: Int,
episode: Int,
requireSkipIntroEnabled: Boolean = true,
): List<SkipInterval> {
if (imdbId == null) return emptyList()
): List<SkipInterval> = coroutineScope {
if (imdbId == null) return@coroutineScope emptyList()
val settings = PlayerSettingsRepository.uiState.value
if (requireSkipIntroEnabled && !settings.skipIntroEnabled) return emptyList()
if (requireSkipIntroEnabled && !settings.skipIntroEnabled) return@coroutineScope emptyList()
val cacheKey = "$imdbId:$season:$episode"
cache[cacheKey]?.let { return it }
cache[cacheKey]?.let { return@coroutineScope it }
val introDb = if (introDbConfigured) fetchFromIntroDb(imdbId, season, episode) else emptyList()
val entries = resolveImdbEntries(imdbId)
val animeSkip = fetchAnimeSkipForEntries(entries, season, episode)
val introDbDeferred = async {
if (introDbConfigured) fetchFromIntroDb(imdbId, season, episode) else emptyList()
}
val entriesDeferred = async { resolveImdbEntries(imdbId) }
val entries = entriesDeferred.await()
val animeSkipDeferred = async { fetchAnimeSkipForEntries(entries, season, episode) }
val malId = entries.getOrNull(season - 1)?.myanimelist?.toString()
?: entries.firstOrNull()?.myanimelist?.toString()
val aniSkip = if (malId != null) fetchFromAniSkip(malId, episode) else emptyList()
val aniSkipDeferred = async {
if (malId != null) fetchFromAniSkip(malId, episode) else emptyList()
}
return mergeByPriority(introDb, animeSkip, aniSkip).also { cache[cacheKey] = it }
return@coroutineScope mergeByPriority(
introDbDeferred.await(),
animeSkipDeferred.await(),
aniSkipDeferred.await(),
).also { cache[cacheKey] = it }
}
suspend fun getSkipIntervalsForMal(
malId: String,
episode: Int,
requireSkipIntroEnabled: Boolean = true,
): List<SkipInterval> {
): List<SkipInterval> = coroutineScope {
val settings = PlayerSettingsRepository.uiState.value
if (requireSkipIntroEnabled && !settings.skipIntroEnabled) return emptyList()
if (requireSkipIntroEnabled && !settings.skipIntroEnabled) return@coroutineScope emptyList()
val cacheKey = "mal:$malId:$episode"
cache[cacheKey]?.let { return it }
cache[cacheKey]?.let { return@coroutineScope it }
val aniSkip = fetchFromAniSkip(malId, episode)
val aniSkipDeferred = async { fetchFromAniSkip(malId, episode) }
val imdbId = try {
SkipIntroApi.resolveMalToImdb(malId)?.imdb
} catch (_: Exception) { null }
val imdbIdDeferred = async {
try {
SkipIntroApi.resolveMalToImdb(malId)?.imdb
} catch (_: Exception) { null }
}
var introDb = emptyList<SkipInterval>()
var animeSkip = emptyList<SkipInterval>()
val imdbId = imdbIdDeferred.await()
if (imdbId != null) {
val entries = resolveImdbEntries(imdbId)
val season = entries.indexOfFirst { it.myanimelist == malId.toIntOrNull() } + 1
if (introDbConfigured) introDb = fetchFromIntroDb(imdbId, season, episode)
animeSkip = fetchAnimeSkipForEntries(entries, season, episode)
val introDbDeferred = async {
if (introDbConfigured) fetchFromIntroDb(imdbId, season, episode) else emptyList()
}
val animeSkipDeferred = async { fetchAnimeSkipForEntries(entries, season, episode) }
introDb = introDbDeferred.await()
animeSkip = animeSkipDeferred.await()
} else {
val anilistId = try {
SkipIntroApi.resolveMalToAnilist(malId)?.anilist?.toString()
@ -66,36 +84,46 @@ object SkipIntroRepository {
if (anilistId != null) animeSkip = fetchFromAnimeSkip(anilistId, episode, season = null)
}
return mergeByPriority(introDb, animeSkip, aniSkip).also { cache[cacheKey] = it }
return@coroutineScope mergeByPriority(introDb, animeSkip, aniSkipDeferred.await()).also { cache[cacheKey] = it }
}
suspend fun getSkipIntervalsForKitsu(
kitsuId: String,
episode: Int,
requireSkipIntroEnabled: Boolean = true,
): List<SkipInterval> {
): List<SkipInterval> = coroutineScope {
val settings = PlayerSettingsRepository.uiState.value
if (requireSkipIntroEnabled && !settings.skipIntroEnabled) return emptyList()
if (requireSkipIntroEnabled && !settings.skipIntroEnabled) return@coroutineScope emptyList()
val cacheKey = "kitsu:$kitsuId:$episode"
cache[cacheKey]?.let { return it }
cache[cacheKey]?.let { return@coroutineScope it }
val malId = try {
SkipIntroApi.resolveKitsuToMal(kitsuId)?.myanimelist?.toString()
} catch (_: Exception) { null }
val aniSkip = if (malId != null) fetchFromAniSkip(malId, episode) else emptyList()
val imdbId = try {
SkipIntroApi.resolveKitsuToImdb(kitsuId)?.imdb
} catch (_: Exception) { null }
val malIdDeferred = async {
try {
SkipIntroApi.resolveKitsuToMal(kitsuId)?.myanimelist?.toString()
} catch (_: Exception) { null }
}
val imdbIdDeferred = async {
try {
SkipIntroApi.resolveKitsuToImdb(kitsuId)?.imdb
} catch (_: Exception) { null }
}
val aniSkipDeferred = async {
malIdDeferred.await()?.let { fetchFromAniSkip(it, episode) } ?: emptyList()
}
var introDb = emptyList<SkipInterval>()
var animeSkip = emptyList<SkipInterval>()
val imdbId = imdbIdDeferred.await()
if (imdbId != null) {
val entries = resolveImdbEntries(imdbId)
val season = entries.indexOfFirst { it.kitsu == kitsuId.toIntOrNull() } + 1
if (introDbConfigured) introDb = fetchFromIntroDb(imdbId, season, episode)
animeSkip = fetchAnimeSkipForEntries(entries, season, episode)
val introDbDeferred = async {
if (introDbConfigured) fetchFromIntroDb(imdbId, season, episode) else emptyList()
}
val animeSkipDeferred = async { fetchAnimeSkipForEntries(entries, season, episode) }
introDb = introDbDeferred.await()
animeSkip = animeSkipDeferred.await()
} else {
val anilistId = try {
SkipIntroApi.resolveKitsuToAnilist(kitsuId)?.anilist?.toString()
@ -103,7 +131,7 @@ object SkipIntroRepository {
if (anilistId != null) animeSkip = fetchFromAnimeSkip(anilistId, episode, season = null)
}
return mergeByPriority(introDb, animeSkip, aniSkip).also { cache[cacheKey] = it }
return@coroutineScope mergeByPriority(introDb, animeSkip, aniSkipDeferred.await()).also { cache[cacheKey] = it }
}
/**