Skip to content

Commit

Permalink
feat(ext): Add support to open video in external player
Browse files Browse the repository at this point in the history
  • Loading branch information
Secozzi committed Jan 25, 2025
1 parent c4451ca commit ca01fc4
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 18 deletions.
57 changes: 51 additions & 6 deletions app/src/main/java/eu/kanade/tachiyomi/ui/player/ExternalIntents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import eu.kanade.domain.track.anime.service.DelayedAnimeTrackingUpdateJob
import eu.kanade.domain.track.anime.store.DelayedAnimeTrackingStore
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.animesource.AnimeSource
import eu.kanade.tachiyomi.animesource.model.Hoster
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
import eu.kanade.tachiyomi.data.track.AnimeTracker
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.ui.player.controls.components.sheets.HosterState
import eu.kanade.tachiyomi.ui.player.loader.EpisodeLoader
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.util.system.LocaleHelper
Expand All @@ -29,6 +31,8 @@ import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.coroutineScope
import logcat.LogPriority
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withIOContext
Expand Down Expand Up @@ -82,10 +86,10 @@ class ExternalIntents {
val hosters = EpisodeLoader.getHosters(episode, anime, source)

val video = chosenVideo
?: EpisodeLoader.getVideos(source, hosters.first()).firstOrNull()
?: getPreferredVideo(source, hosters)
?: throw Exception("Video list is empty")

val videoUrl = getVideoUrl(context, video) ?: return null
val videoUrl = getVideoUrl(source, context, video) ?: return null

val pkgName = playerPreferences.externalPlayerPreference().get()

Expand All @@ -100,18 +104,59 @@ class ExternalIntents {
}
}

private suspend fun getPreferredVideo(source: AnimeSource, hosterList: List<Hoster>): Video? {
val hosterStates = MutableList<HosterState>(hosterList.size) { HosterState.Idle("") }
return coroutineScope {
val deferredHosters = hosterList.map { hoster ->
async {
EpisodeLoader.loadHosterVideos(source, hoster)
}
}

deferredHosters.forEachIndexed { hosterIdx, dHoster ->
val hosterState = dHoster.await()
hosterStates[hosterIdx] = hosterState

if (hosterState is HosterState.Ready) {
val prefIndex = hosterState.videoList.indexOfFirst { it.preferred }
if (prefIndex != -1) {
val video = hosterState.videoList[prefIndex]
coroutineContext.cancelChildren()
return@coroutineScope video
}
}
}

val firstValidHosterIdx = hosterStates.indexOfFirst { hoster ->
hoster is HosterState.Ready && hoster.videoList.any { v -> v.videoUrl.isNotEmpty() }
}
if (firstValidHosterIdx != -1) {
val videoList = (hosterStates[firstValidHosterIdx] as HosterState.Ready).videoList
videoList.first { it.videoUrl.isNotEmpty() }
} else {
null
}
}
}

/**
* Returns the [Uri] of the given video.
*
* @param context the application context.
* @param video the video being sent to the external player.
*/
private suspend fun getVideoUrl(context: Context, video: Video): Uri? {
if (video.videoUrl == null) {
makeErrorToast(context, Exception("Video URL is null."))
private suspend fun getVideoUrl(source: AnimeSource, context: Context, video: Video): Uri? {
val newVideoUrl = if (source is AnimeHttpSource) {
source.resolveVideoUrl(video)
} else {
video.videoUrl
}

if (newVideoUrl.isEmpty()) {
makeErrorToast(context, Exception("Video URL is empty."))
return null
} else {
val uri = video.videoUrl!!.toUri()
val uri = newVideoUrl.toUri()

val isOnDevice = if (anime.source == LocalAnimeSource.ID) {
true
Expand Down
14 changes: 2 additions & 12 deletions app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1262,7 +1262,7 @@ class PlayerViewModel @JvmOverloads constructor(
coroutineScope {
val deferredHosters = hosterList.map { hoster ->
async {
loadHosterVideos(source, hoster)
EpisodeLoader.loadHosterVideos(source, hoster)
}
}

Expand Down Expand Up @@ -1365,16 +1365,6 @@ class PlayerViewModel @JvmOverloads constructor(
throw Exception("No available videos")
}

private suspend fun loadHosterVideos(source: AnimeSource, hoster: Hoster): HosterState {
return try {
val videos = EpisodeLoader.getVideos(source, hoster)
HosterState.Ready(hoster.hosterName, videos, List(videos.size) { Video.State.QUEUE })
} catch (e: Exception) {
currentCoroutineContext().ensureActive()
HosterState.Error(hoster.hosterName)
}
}

private fun HosterState.Ready.getChangedAt(index: Int, newVideo: Video, newState: Video.State): HosterState.Ready {
return HosterState.Ready(
name = this.name,
Expand Down Expand Up @@ -1481,7 +1471,7 @@ class PlayerViewModel @JvmOverloads constructor(
_hosterState.updateAt(index, HosterState.Loading(hosterName))

viewModelScope.launch(Dispatchers.IO) {
val hosterState = loadHosterVideos(currentSource.value!!, hosterList.value[index])
val hosterState = EpisodeLoader.loadHosterVideos(currentSource.value!!, hosterList.value[index])
_hosterState.updateAt(index, hosterState)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import eu.kanade.tachiyomi.animesource.model.Hoster.Companion.toHosterList
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
import eu.kanade.tachiyomi.ui.player.controls.components.sheets.HosterState
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import tachiyomi.domain.entries.anime.model.Anime
import tachiyomi.domain.items.episode.model.Episode
import tachiyomi.source.local.entries.anime.LocalAnimeSource
Expand Down Expand Up @@ -155,5 +158,15 @@ class EpisodeLoader {
video.copy(videoUrl = newVideoUrl)
}
}

suspend fun loadHosterVideos(source: AnimeSource, hoster: Hoster): HosterState {
return try {
val videos = getVideos(source, hoster)
HosterState.Ready(hoster.hosterName, videos, List(videos.size) { Video.State.QUEUE })
} catch (e: Exception) {
currentCoroutineContext().ensureActive()
HosterState.Error(hoster.hosterName)
}
}
}
}

0 comments on commit ca01fc4

Please sign in to comment.