2022-08-01 01:00:48 +00:00
package com.lagradost.cloudstream3.ui.result
2022-08-02 00:43:42 +00:00
import android.app.Activity
2022-10-05 22:14:42 +00:00
import android.content.*
2022-08-02 00:43:42 +00:00
import android.net.Uri
2022-10-08 15:48:46 +00:00
import android.os.Bundle
2022-08-01 02:46:43 +00:00
import android.util.Log
2022-08-02 00:43:42 +00:00
import android.widget.Toast
import androidx.core.content.FileProvider
2022-10-08 14:56:05 +00:00
import androidx.core.net.toUri
2022-08-01 01:00:48 +00:00
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.APIHolder.unixTime
2022-10-08 20:29:17 +00:00
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
2022-08-02 00:43:42 +00:00
import com.lagradost.cloudstream3.CommonActivity.getCastSession
2022-08-03 01:02:08 +00:00
import com.lagradost.cloudstream3.CommonActivity.showToast
2022-08-01 02:46:43 +00:00
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId
import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
2022-08-02 00:43:42 +00:00
import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie
2022-08-01 01:00:48 +00:00
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
import com.lagradost.cloudstream3.mvvm.*
2022-08-01 02:46:43 +00:00
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
2022-08-01 01:00:48 +00:00
import com.lagradost.cloudstream3.ui.APIRepository
2022-08-02 00:43:42 +00:00
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
2022-08-01 01:00:48 +00:00
import com.lagradost.cloudstream3.ui.player.IGenerator
2022-08-02 00:43:42 +00:00
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
import com.lagradost.cloudstream3.ui.player.SubtitleData
2022-10-05 22:14:42 +00:00
import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction
2022-08-02 00:43:42 +00:00
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
2022-08-01 02:46:43 +00:00
import com.lagradost.cloudstream3.utils.*
2022-08-03 00:04:03 +00:00
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
2022-08-03 01:02:08 +00:00
import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
2022-08-02 00:43:42 +00:00
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
import com.lagradost.cloudstream3.utils.CastHelper.startCast
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
2022-08-04 01:19:59 +00:00
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
2022-08-18 00:54:05 +00:00
import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe
2022-08-07 23:03:54 +00:00
import com.lagradost.cloudstream3.utils.Coroutines.main
2022-08-04 01:19:59 +00:00
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
2022-08-03 00:04:03 +00:00
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
2022-08-03 17:27:49 +00:00
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
2022-08-04 01:19:59 +00:00
import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
2022-08-02 00:43:42 +00:00
import com.lagradost.cloudstream3.utils.UIHelper.navigate
2022-08-03 00:04:03 +00:00
import kotlinx.coroutines.*
2022-08-02 00:43:42 +00:00
import java.io.File
2022-08-05 23:41:35 +00:00
import java.lang.Math.abs
2022-08-01 01:00:48 +00:00
import java.util.concurrent.TimeUnit
/** This starts at 1 */
data class EpisodeRange (
// used to index data
val startIndex : Int ,
val length : Int ,
// used to display data
val startEpisode : Int ,
val endEpisode : Int ,
)
2022-08-04 01:19:59 +00:00
data class AutoResume (
val season : Int ? ,
val episode : Int ? ,
val id : Int ? ,
val startAction : Int ,
)
2022-08-01 01:00:48 +00:00
data class ResultData (
val url : String ,
val tags : List < String > ,
val actors : List < ActorData > ? ,
val actorsText : UiText ? ,
val comingSoon : Boolean ,
val backgroundPosterUrl : String ? ,
val title : String ,
2022-08-02 00:43:42 +00:00
var syncData : Map < String , String > ,
2022-08-01 01:00:48 +00:00
val posterImage : UiImage ? ,
val plotText : UiText ,
val apiName : UiText ,
val ratingText : UiText ? ,
val vpnText : UiText ? ,
val metaText : UiText ? ,
val durationText : UiText ? ,
val onGoingText : UiText ? ,
val noEpisodesFoundText : UiText ? ,
val titleText : UiText ,
val typeText : UiText ,
val yearText : UiText ? ,
val nextAiringDate : UiText ? ,
val nextAiringEpisode : UiText ? ,
2022-08-01 02:46:43 +00:00
val plotHeaderText : UiText ,
2022-08-01 01:00:48 +00:00
)
2022-08-01 02:46:43 +00:00
fun txt ( status : DubStatus ? ) : UiText ? {
return txt (
when ( status ) {
DubStatus . Dubbed -> R . string . app _dubbed _text
DubStatus . Subbed -> R . string . app _subbed _text
else -> null
}
)
}
2022-08-01 01:00:48 +00:00
fun LoadResponse . toResultData ( repo : APIRepository ) : ResultData {
2022-08-02 00:43:42 +00:00
debugAssert ( { repo . name != apiName } ) {
2022-08-01 01:00:48 +00:00
" Api returned wrong apiName "
}
val hasActorImages = actors ?. firstOrNull ( ) ?. actor ?. image ?. isNotBlank ( ) == true
var nextAiringEpisode : UiText ? = null
var nextAiringDate : UiText ? = null
if ( this is EpisodeResponse ) {
val airing = this . nextAiring
if ( airing != null && airing . unixTime > unixTime ) {
val seconds = airing . unixTime - unixTime
val days = TimeUnit . SECONDS . toDays ( seconds )
val hours : Long = TimeUnit . SECONDS . toHours ( seconds ) - days * 24
val minute =
TimeUnit . SECONDS . toMinutes ( seconds ) - TimeUnit . SECONDS . toHours ( seconds ) * 60
2022-08-03 00:04:03 +00:00
nextAiringDate = when {
2022-08-01 01:00:48 +00:00
days > 0 -> {
txt (
R . string . next _episode _time _day _format ,
days ,
hours ,
minute
)
}
hours > 0 -> txt (
R . string . next _episode _time _hour _format ,
hours ,
minute
)
minute > 0 -> txt (
R . string . next _episode _time _min _format ,
minute
)
else -> null
} ?. also {
2022-08-03 00:04:03 +00:00
nextAiringEpisode = txt ( R . string . next _episode _format , airing . episode )
2022-08-01 01:00:48 +00:00
}
}
}
2022-08-03 00:04:03 +00:00
val dur = duration
2022-08-01 01:00:48 +00:00
return ResultData (
2022-08-02 00:43:42 +00:00
syncData = syncData ,
2022-08-01 02:46:43 +00:00
plotHeaderText = txt (
when ( this . type ) {
TvType . Torrent -> R . string . torrent _plot
else -> R . string . result _plot
}
) ,
2022-08-01 01:00:48 +00:00
nextAiringDate = nextAiringDate ,
nextAiringEpisode = nextAiringEpisode ,
posterImage = img (
posterUrl , posterHeaders
) ?: img ( R . drawable . default _cover ) ,
titleText = txt ( name ) ,
url = url ,
tags = tags ?: emptyList ( ) ,
comingSoon = comingSoon ,
actors = if ( hasActorImages ) actors else null ,
2022-08-04 14:03:13 +00:00
actorsText = if ( hasActorImages || actors . isNullOrEmpty ( ) ) null else txt (
2022-08-01 01:00:48 +00:00
R . string . cast _format ,
actors ?. joinToString { it . actor . name } ) ,
plotText =
if ( plot . isNullOrBlank ( ) ) txt ( if ( this is TorrentLoadResponse ) R . string . torrent _no _plot else R . string . normal _no _plot ) else txt (
plot !!
) ,
backgroundPosterUrl = backgroundPosterUrl ,
title = name ,
typeText = txt (
when ( type ) {
TvType . TvSeries -> R . string . tv _series _singular
TvType . Anime -> R . string . anime _singular
TvType . OVA -> R . string . ova _singular
TvType . AnimeMovie -> R . string . movies _singular
TvType . Cartoon -> R . string . cartoons _singular
TvType . Documentary -> R . string . documentaries _singular
TvType . Movie -> R . string . movies _singular
TvType . Torrent -> R . string . torrent _singular
TvType . AsianDrama -> R . string . asian _drama _singular
TvType . Live -> R . string . live _singular
2022-08-14 11:49:14 +00:00
TvType . Others -> R . string . other _singular
2022-08-14 14:00:10 +00:00
TvType . NSFW -> R . string . nsfw _singular
2022-08-01 01:00:48 +00:00
}
) ,
2022-08-03 00:04:03 +00:00
yearText = txt ( year ?. toString ( ) ) ,
2022-08-01 01:00:48 +00:00
apiName = txt ( apiName ) ,
2022-08-06 18:51:32 +00:00
ratingText = rating ?. div ( 1000f )
?. let { if ( it <= 0.1f ) null else txt ( R . string . rating _format , it ) } ,
2022-08-01 01:00:48 +00:00
vpnText = txt (
when ( repo . vpnStatus ) {
VPNStatus . None -> null
VPNStatus . Torrent -> R . string . vpn _torrent
VPNStatus . MightBeNeeded -> R . string . vpn _might _be _needed
}
) ,
metaText =
if ( repo . providerType == ProviderType . MetaProvider ) txt ( R . string . provider _info _meta ) else null ,
2022-08-03 00:04:03 +00:00
durationText = if ( dur == null || dur <= 0 ) null else txt (
R . string . duration _format ,
dur
) ,
2022-08-01 01:00:48 +00:00
onGoingText = if ( this is EpisodeResponse ) {
txt (
when ( showStatus ) {
ShowStatus . Ongoing -> R . string . status _ongoing
ShowStatus . Completed -> R . string . status _completed
else -> null
}
)
} else null ,
noEpisodesFoundText =
if ( ( this is TvSeriesLoadResponse && this . episodes . isEmpty ( ) ) || ( this is AnimeLoadResponse && ! this . episodes . any { it . value . isNotEmpty ( ) } ) ) txt (
R . string . no _episodes _found
) else null
)
}
2022-08-02 00:43:42 +00:00
data class LinkProgress (
val linksLoaded : Int ,
val subsLoaded : Int ,
)
2022-08-03 17:27:49 +00:00
data class ResumeProgress (
val progress : Int ,
val maxProgress : Int ,
val progressLeft : UiText ,
)
data class ResumeWatchingStatus (
val progress : ResumeProgress ? ,
val isMovie : Boolean ,
val result : ResultEpisode ,
)
2022-08-02 00:43:42 +00:00
data class LinkLoadingResult (
val links : List < ExtractorLink > ,
val subs : List < SubtitleData > ,
)
sealed class SelectPopup {
data class SelectText (
val text : UiText ,
val options : List < UiText > ,
val callback : ( Int ? ) -> Unit
) : SelectPopup ( )
data class SelectArray (
val text : UiText ,
2022-08-03 01:02:08 +00:00
val options : List < Pair < UiText , Int > > ,
2022-08-02 00:43:42 +00:00
val callback : ( Int ? ) -> Unit
) : SelectPopup ( )
2022-08-03 00:04:03 +00:00
}
2022-08-02 00:43:42 +00:00
2022-08-03 01:02:08 +00:00
fun SelectPopup . callback ( index : Int ? ) {
val ret = transformResult ( index )
2022-08-03 00:04:03 +00:00
return when ( this ) {
is SelectPopup . SelectArray -> callback ( ret )
is SelectPopup . SelectText -> callback ( ret )
2022-08-02 00:43:42 +00:00
}
2022-08-03 00:04:03 +00:00
}
2022-08-02 00:43:42 +00:00
2022-08-03 01:02:08 +00:00
fun SelectPopup . transformResult ( input : Int ? ) : Int ? {
2022-08-03 00:04:03 +00:00
if ( input == null ) return null
return when ( this ) {
2022-08-03 01:02:08 +00:00
is SelectPopup . SelectArray -> options . getOrNull ( input ) ?. second
2022-08-03 00:04:03 +00:00
is SelectPopup . SelectText -> input
}
}
fun SelectPopup . getTitle ( context : Context ) : String {
return when ( this ) {
is SelectPopup . SelectArray -> text . asString ( context )
is SelectPopup . SelectText -> text . asString ( context )
}
}
fun SelectPopup . getOptions ( context : Context ) : List < String > {
return when ( this ) {
is SelectPopup . SelectArray -> {
2022-08-03 01:02:08 +00:00
this . options . map { it . first . asString ( context ) }
2022-08-02 00:43:42 +00:00
}
2022-08-03 00:04:03 +00:00
is SelectPopup . SelectText -> options . map { it . asString ( context ) }
2022-08-02 00:43:42 +00:00
}
}
2022-08-25 01:59:20 +00:00
data class ExtractedTrailerData (
var mirros : List < ExtractorLink > ,
var subtitles : List < SubtitleFile > = emptyList ( ) ,
)
2022-08-01 01:00:48 +00:00
class ResultViewModel2 : ViewModel ( ) {
private var currentResponse : LoadResponse ? = null
data class EpisodeIndexer (
val dubStatus : DubStatus ,
val season : Int ,
)
/** map<dub, map<season, List<episode>>> */
private var currentEpisodes : Map < EpisodeIndexer , List < ResultEpisode > > = mapOf ( )
private var currentRanges : Map < EpisodeIndexer , List < EpisodeRange > > = mapOf ( )
2022-08-05 23:41:35 +00:00
private var currentSeasons : List < Int > = listOf ( )
private var currentDubStatus : List < DubStatus > = listOf ( )
2022-08-01 02:46:43 +00:00
private var currentMeta : SyncAPI . SyncResult ? = null
private var currentSync : Map < String , String > ? = null
2022-08-01 01:00:48 +00:00
private var currentIndex : EpisodeIndexer ? = null
private var currentRange : EpisodeRange ? = null
private var currentShowFillers : Boolean = false
private var currentRepo : APIRepository ? = null
private var currentId : Int ? = null
private var fillers : Map < Int , Boolean > = emptyMap ( )
private var generator : IGenerator ? = null
private var preferDubStatus : DubStatus ? = null
private var preferStartEpisode : Int ? = null
private var preferStartSeason : Int ? = null
2022-08-02 00:43:42 +00:00
//private val currentIsMovie get() = currentResponse?.isEpisodeBased() == false
//private val currentHeaderName get() = currentResponse?.name
2022-08-01 01:00:48 +00:00
private val _page : MutableLiveData < Resource < ResultData > > =
MutableLiveData ( Resource . Loading ( ) )
val page : LiveData < Resource < ResultData > > = _page
2022-08-03 00:04:03 +00:00
private val _episodes : MutableLiveData < ResourceSome < List < ResultEpisode > > > =
MutableLiveData ( ResourceSome . Loading ( ) )
val episodes : LiveData < ResourceSome < List < ResultEpisode > > > = _episodes
private val _movie : MutableLiveData < ResourceSome < Pair < UiText , ResultEpisode > > > =
MutableLiveData ( ResourceSome . None )
val movie : LiveData < ResourceSome < Pair < UiText , ResultEpisode > > > = _movie
2022-08-01 01:00:48 +00:00
2022-08-03 00:04:03 +00:00
private val _episodesCountText : MutableLiveData < Some < UiText > > =
MutableLiveData ( Some . None )
val episodesCountText : LiveData < Some < UiText > > = _episodesCountText
2022-08-01 01:00:48 +00:00
2022-08-25 01:59:20 +00:00
private val _trailers : MutableLiveData < List < ExtractedTrailerData > > =
MutableLiveData ( mutableListOf ( ) )
val trailers : LiveData < List < ExtractedTrailerData > > = _trailers
2022-08-01 01:00:48 +00:00
2022-08-01 02:46:43 +00:00
private val _dubSubSelections : MutableLiveData < List < Pair < UiText ? , DubStatus > > > =
MutableLiveData ( emptyList ( ) )
val dubSubSelections : LiveData < List < Pair < UiText ? , DubStatus > > > = _dubSubSelections
2022-08-02 00:43:42 +00:00
private val _rangeSelections : MutableLiveData < List < Pair < UiText ? , EpisodeRange > > > =
MutableLiveData ( emptyList ( ) )
2022-08-01 02:46:43 +00:00
val rangeSelections : LiveData < List < Pair < UiText ? , EpisodeRange > > > = _rangeSelections
2022-08-02 00:43:42 +00:00
private val _seasonSelections : MutableLiveData < List < Pair < UiText ? , Int > > > =
MutableLiveData ( emptyList ( ) )
2022-08-01 02:46:43 +00:00
val seasonSelections : LiveData < List < Pair < UiText ? , Int > > > = _seasonSelections
private val _recommendations : MutableLiveData < List < SearchResponse > > =
MutableLiveData ( emptyList ( ) )
val recommendations : LiveData < List < SearchResponse > > = _recommendations
2022-08-03 00:04:03 +00:00
private val _selectedRange : MutableLiveData < Some < UiText > > =
MutableLiveData ( Some . None )
val selectedRange : LiveData < Some < UiText > > = _selectedRange
2022-08-01 02:46:43 +00:00
2022-08-03 00:04:03 +00:00
private val _selectedSeason : MutableLiveData < Some < UiText > > =
MutableLiveData ( Some . None )
val selectedSeason : LiveData < Some < UiText > > = _selectedSeason
2022-08-01 02:46:43 +00:00
2022-08-03 00:04:03 +00:00
private val _selectedDubStatus : MutableLiveData < Some < UiText > > = MutableLiveData ( Some . None )
val selectedDubStatus : LiveData < Some < UiText > > = _selectedDubStatus
2022-08-01 01:00:48 +00:00
2022-08-05 23:41:35 +00:00
private val _selectedRangeIndex : MutableLiveData < Int > =
MutableLiveData ( - 1 )
val selectedRangeIndex : LiveData < Int > = _selectedRangeIndex
private val _selectedSeasonIndex : MutableLiveData < Int > =
MutableLiveData ( - 1 )
val selectedSeasonIndex : LiveData < Int > = _selectedSeasonIndex
private val _selectedDubStatusIndex : MutableLiveData < Int > = MutableLiveData ( - 1 )
val selectedDubStatusIndex : LiveData < Int > = _selectedDubStatusIndex
2022-08-03 00:04:03 +00:00
private val _loadedLinks : MutableLiveData < Some < LinkProgress > > = MutableLiveData ( Some . None )
val loadedLinks : LiveData < Some < LinkProgress > > = _loadedLinks
2022-08-02 00:43:42 +00:00
2022-08-03 17:27:49 +00:00
private val _resumeWatching : MutableLiveData < Some < ResumeWatchingStatus > > =
MutableLiveData ( Some . None )
val resumeWatching : LiveData < Some < ResumeWatchingStatus > > = _resumeWatching
2022-08-01 01:00:48 +00:00
companion object {
2022-08-01 02:46:43 +00:00
const val TAG = " RVM2 "
2022-08-04 01:19:59 +00:00
private const val EPISODE _RANGE _SIZE = 20
private const val EPISODE _RANGE _OVERLOAD = 30
2022-08-01 01:00:48 +00:00
2022-09-10 17:59:37 +00:00
private fun List < SeasonData > ?. getSeason ( season : Int ? ) : SeasonData ? {
if ( season == null ) return null
return this ?. firstOrNull { it . season == season }
}
2022-10-28 01:51:27 +00:00
fun updateWatchStatus ( currentResponse : LoadResponse , status : WatchType ) {
val currentId = currentResponse . getId ( )
val resultPage = currentResponse
DataStoreHelper . setResultWatchState ( currentId , status . internalId )
val current = DataStoreHelper . getBookmarkedData ( currentId )
val currentTime = System . currentTimeMillis ( )
DataStoreHelper . setBookmarkedData (
currentId ,
DataStoreHelper . BookmarkedData (
currentId ,
current ?. bookmarkedTime ?: currentTime ,
currentTime ,
resultPage . name ,
resultPage . url ,
resultPage . apiName ,
resultPage . type ,
resultPage . posterUrl ,
resultPage . year
)
)
}
2022-08-01 01:00:48 +00:00
private fun filterName ( name : String ? ) : String ? {
if ( name == null ) return null
Regex ( " [eE]pisode [0-9]*(.*) " ) . find ( name ) ?. groupValues ?. get ( 1 ) ?. let {
if ( it . isEmpty ( ) )
return null
}
return name
}
fun singleMap ( ep : ResultEpisode ) : Map < EpisodeIndexer , List < ResultEpisode > > =
mapOf (
EpisodeIndexer ( DubStatus . None , 0 ) to listOf (
ep
)
)
private fun getRanges ( allEpisodes : Map < EpisodeIndexer , List < ResultEpisode > > ) : Map < EpisodeIndexer , List < EpisodeRange > > {
return allEpisodes . keys . mapNotNull { index ->
val episodes =
allEpisodes [ index ] ?: return @mapNotNull null // this should never happened
// fast case
if ( episodes . size <= EPISODE _RANGE _OVERLOAD ) {
return @mapNotNull index to listOf (
EpisodeRange (
0 ,
episodes . size ,
episodes . minOf { it . episode } ,
episodes . maxOf { it . episode } )
)
}
if ( episodes . isEmpty ( ) ) {
return @mapNotNull null
}
val list = mutableListOf < EpisodeRange > ( )
val currentEpisode = episodes . first ( )
var currentIndex = 0
val maxIndex = episodes . size
var targetEpisode = 0
var currentMin = currentEpisode . episode
var currentMax = currentEpisode . episode
while ( currentIndex < maxIndex ) {
val startIndex = currentIndex
targetEpisode += EPISODE _RANGE _SIZE
while ( currentIndex < maxIndex && episodes [ currentIndex ] . episode <= targetEpisode ) {
val episodeNumber = episodes [ currentIndex ] . episode
if ( episodeNumber < currentMin ) {
currentMin = episodeNumber
} else if ( episodeNumber > currentMax ) {
currentMax = episodeNumber
}
++ currentIndex
}
val length = currentIndex - startIndex
if ( length <= 0 ) continue
list . add (
EpisodeRange (
startIndex ,
length ,
currentMin ,
currentMax
)
)
2022-08-04 01:19:59 +00:00
currentMin = Int . MAX _VALUE
currentMax = Int . MIN _VALUE
2022-08-01 01:00:48 +00:00
}
/ * var currentMin = Int . MAX _VALUE
var currentMax = Int . MIN _VALUE
var currentStartIndex = 0
var currentLength = 0
for ( ep in episodes ) {
val episodeNumber = ep . episode
if ( episodeNumber < currentMin ) {
currentMin = episodeNumber
} else if ( episodeNumber > currentMax ) {
currentMax = episodeNumber
}
if ( ++ currentLength >= EPISODE _RANGE _SIZE ) {
list . add (
EpisodeRange (
currentStartIndex ,
currentLength ,
currentMin ,
currentMax
)
)
currentMin = Int . MAX _VALUE
currentMax = Int . MIN _VALUE
currentStartIndex += currentLength
currentLength = 0
}
}
if ( currentLength > 0 ) {
list . add (
EpisodeRange (
currentStartIndex ,
currentLength ,
currentMin ,
currentMax
)
)
} * /
index to list
} . toMap ( )
}
2022-08-02 00:43:42 +00:00
private fun downloadSubtitle (
context : Context ? ,
link : ExtractorSubtitleLink ,
fileName : String ,
folder : String
) {
ioSafe {
VideoDownloadManager . downloadThing (
context ?: return @ioSafe ,
link ,
" $fileName ${link.name} " ,
folder ,
if ( link . url . contains ( " .srt " ) ) " .srt " else " vtt " ,
false ,
null
) {
// no notification
}
}
}
private fun getFolder ( currentType : TvType , titleName : String ) : String {
val sanitizedFileName = VideoDownloadManager . sanitizeFilename ( titleName )
return when ( currentType ) {
TvType . Anime -> " Anime/ $sanitizedFileName "
TvType . Movie -> " Movies "
TvType . AnimeMovie -> " Movies "
TvType . TvSeries -> " TVSeries/ $sanitizedFileName "
TvType . OVA -> " OVA "
TvType . Cartoon -> " Cartoons/ $sanitizedFileName "
TvType . Torrent -> " Torrent "
TvType . Documentary -> " Documentaries "
TvType . AsianDrama -> " AsianDrama "
TvType . Live -> " LiveStreams "
2022-08-14 14:00:10 +00:00
TvType . NSFW -> " NSFW "
2022-08-14 11:49:14 +00:00
TvType . Others -> " Others "
2022-08-02 00:43:42 +00:00
}
}
private fun downloadSubtitle (
context : Context ? ,
link : SubtitleData ,
meta : VideoDownloadManager . DownloadEpisodeMetadata ,
) {
context ?. let { ctx ->
val fileName = VideoDownloadManager . getFileName ( ctx , meta )
val folder = getFolder ( meta . type ?: return , meta . mainName )
downloadSubtitle (
ctx ,
ExtractorSubtitleLink ( link . name , link . url , " " ) ,
fileName ,
folder
)
}
}
fun startDownload (
context : Context ? ,
episode : ResultEpisode ,
currentIsMovie : Boolean ,
currentHeaderName : String ,
currentType : TvType ,
currentPoster : String ? ,
apiName : String ,
parentId : Int ,
url : String ,
links : List < ExtractorLink > ,
subs : List < SubtitleData > ?
) {
try {
if ( context == null ) return
val meta =
getMeta (
episode ,
currentHeaderName ,
apiName ,
currentPoster ,
currentIsMovie ,
currentType
)
val folder = getFolder ( currentType , currentHeaderName )
val src = " $DOWNLOAD _NAVIGATE_TO/ $parentId " // url ?: return@let
// SET VISUAL KEYS
2022-10-08 20:29:17 +00:00
setKey (
2022-08-02 00:43:42 +00:00
DOWNLOAD _HEADER _CACHE ,
parentId . toString ( ) ,
VideoDownloadHelper . DownloadHeaderCached (
apiName ,
url ,
currentType ,
currentHeaderName ,
currentPoster ,
parentId ,
System . currentTimeMillis ( ) ,
)
)
2022-10-08 20:29:17 +00:00
setKey (
2022-08-02 00:43:42 +00:00
DataStore . getFolderName (
DOWNLOAD _EPISODE _CACHE ,
parentId . toString ( )
) , // 3 deep folder for faster acess
episode . id . toString ( ) ,
VideoDownloadHelper . DownloadEpisodeCached (
episode . name ,
episode . poster ,
episode . episode ,
episode . season ,
episode . id ,
parentId ,
episode . rating ,
episode . description ,
System . currentTimeMillis ( ) ,
)
)
// DOWNLOAD VIDEO
VideoDownloadManager . downloadEpisodeUsingWorker (
context ,
src , //url ?: return,
folder ,
meta ,
links
)
// 1. Checks if the lang should be downloaded
// 2. Makes it into the download format
// 3. Downloads it as a .vtt file
val downloadList = SubtitlesFragment . getDownloadSubsLanguageISO639 _1 ( )
subs ?. let { subsList ->
subsList . filter {
downloadList . contains (
SubtitleHelper . fromLanguageToTwoLetters (
it . name ,
true
)
)
}
. map { ExtractorSubtitleLink ( it . name , it . url , " " ) }
. forEach { link ->
val fileName = VideoDownloadManager . getFileName ( context , meta )
downloadSubtitle ( context , link , fileName , folder )
}
}
} catch ( e : Exception ) {
logError ( e )
}
}
suspend fun downloadEpisode (
activity : Activity ? ,
episode : ResultEpisode ,
currentIsMovie : Boolean ,
currentHeaderName : String ,
currentType : TvType ,
currentPoster : String ? ,
apiName : String ,
parentId : Int ,
url : String ,
) {
2022-08-03 01:02:08 +00:00
ioSafe {
2022-08-02 00:43:42 +00:00
val generator = RepoLinkGenerator ( listOf ( episode ) )
val currentLinks = mutableSetOf < ExtractorLink > ( )
val currentSubs = mutableSetOf < SubtitleData > ( )
generator . generateLinks ( clearCache = false , isCasting = false , callback = {
it . first ?. let { link ->
currentLinks . add ( link )
}
} , subtitleCallback = { sub ->
currentSubs . add ( sub )
} )
if ( currentLinks . isEmpty ( ) ) {
2022-08-07 23:03:54 +00:00
main {
2022-08-03 17:27:49 +00:00
showToast (
2022-08-02 00:43:42 +00:00
activity ,
R . string . no _links _found _toast ,
Toast . LENGTH _SHORT
)
}
2022-08-03 01:02:08 +00:00
return @ioSafe
} else {
2022-08-07 23:03:54 +00:00
main {
2022-08-03 17:27:49 +00:00
showToast (
2022-08-03 01:02:08 +00:00
activity ,
R . string . download _started ,
Toast . LENGTH _SHORT
)
}
2022-08-02 00:43:42 +00:00
}
startDownload (
activity ,
episode ,
currentIsMovie ,
currentHeaderName ,
currentType ,
currentPoster ,
apiName ,
parentId ,
url ,
sortUrls ( currentLinks ) ,
sortSubs ( currentSubs ) ,
)
}
}
private fun getMeta (
episode : ResultEpisode ,
titleName : String ,
apiName : String ,
currentPoster : String ? ,
currentIsMovie : Boolean ,
tvType : TvType ,
) : VideoDownloadManager . DownloadEpisodeMetadata {
return VideoDownloadManager . DownloadEpisodeMetadata (
episode . id ,
VideoDownloadManager . sanitizeFilename ( titleName ) ,
apiName ,
episode . poster ?: currentPoster ,
episode . name ,
if ( currentIsMovie ) null else episode . season ,
if ( currentIsMovie ) null else episode . episode ,
tvType ,
)
}
}
private val _watchStatus : MutableLiveData < WatchType > = MutableLiveData ( WatchType . NONE )
val watchStatus : LiveData < WatchType > get ( ) = _watchStatus
2022-08-03 00:04:03 +00:00
private val _selectPopup : MutableLiveData < Some < SelectPopup > > = MutableLiveData ( Some . None )
val selectPopup : LiveData < Some < SelectPopup > > get ( ) = _selectPopup
2022-08-02 00:43:42 +00:00
2022-10-28 01:51:27 +00:00
2022-08-02 00:43:42 +00:00
fun updateWatchStatus ( status : WatchType ) {
2022-10-28 01:51:27 +00:00
updateWatchStatus ( currentResponse ?: return , status )
2022-08-02 00:43:42 +00:00
_watchStatus . postValue ( status )
}
2022-08-03 00:04:03 +00:00
private fun startChromecast (
2022-08-02 00:43:42 +00:00
activity : Activity ? ,
result : ResultEpisode ,
isVisible : Boolean = true
) {
if ( activity == null ) return
2022-08-03 00:04:03 +00:00
loadLinks ( result , isVisible = isVisible , isCasting = true ) { data ->
startChromecast ( activity , result , data . links , data . subs , 0 )
}
2022-08-02 00:43:42 +00:00
}
private fun startChromecast (
activity : Activity ? ,
result : ResultEpisode ,
links : List < ExtractorLink > ,
subs : List < SubtitleData > ,
startIndex : Int ,
) {
if ( activity == null ) return
val response = currentResponse ?: return
val eps = currentEpisodes [ currentIndex ?: return ] ?: return
2022-08-08 12:37:46 +00:00
// Main needed because getCastSession needs to be on main thread
main {
activity . getCastSession ( ) ?. startCast (
response . apiName ,
response . isMovie ( ) ,
response . name ,
response . posterUrl ,
result . index ,
eps ,
links ,
subs ,
startTime = result . getRealPosition ( ) ,
startIndex = startIndex
)
}
2022-08-02 00:43:42 +00:00
}
fun cancelLinks ( ) {
2022-08-03 00:04:03 +00:00
println ( " called::cancelLinks " )
currentLoadLinkJob ?. cancel ( )
currentLoadLinkJob = null
_loadedLinks . postValue ( Some . None )
}
private fun postPopup ( text : UiText , options : List < UiText > , callback : suspend ( Int ? ) -> Unit ) {
_selectPopup . postValue (
some ( SelectPopup . SelectText (
text ,
options
) { value ->
2022-08-18 00:54:05 +00:00
viewModelScope . launchSafe {
2022-08-03 00:04:03 +00:00
_selectPopup . postValue ( Some . None )
callback . invoke ( value )
}
} )
)
}
2022-08-03 01:02:08 +00:00
@JvmName ( " postPopupArray " )
2022-08-03 00:04:03 +00:00
private fun postPopup (
text : UiText ,
2022-08-03 01:02:08 +00:00
options : List < Pair < UiText , Int > > ,
2022-08-03 00:04:03 +00:00
callback : suspend ( Int ? ) -> Unit
) {
_selectPopup . postValue (
some ( SelectPopup . SelectArray (
text ,
options ,
) { value ->
2022-08-18 00:54:05 +00:00
viewModelScope . launchSafe {
2022-08-03 00:04:03 +00:00
_selectPopup . value = Some . None
callback . invoke ( value )
}
} )
)
}
2022-08-18 00:54:05 +00:00
private fun loadLinks (
2022-08-03 00:04:03 +00:00
result : ResultEpisode ,
isVisible : Boolean ,
isCasting : Boolean ,
clearCache : Boolean = false ,
work : suspend ( CoroutineScope . ( LinkLoadingResult ) -> Unit )
) {
2022-08-02 00:43:42 +00:00
currentLoadLinkJob ?. cancel ( )
2022-08-03 00:04:03 +00:00
currentLoadLinkJob = ioSafe {
val links = loadLinks (
result ,
isVisible = isVisible ,
isCasting = isCasting ,
clearCache = clearCache
)
if ( ! this . isActive ) return @ioSafe
work ( links )
}
2022-08-02 00:43:42 +00:00
}
private var currentLoadLinkJob : Job ? = null
2022-08-03 00:04:03 +00:00
private fun acquireSingleLink (
2022-08-02 00:43:42 +00:00
result : ResultEpisode ,
isCasting : Boolean ,
text : UiText ,
callback : ( Pair < LinkLoadingResult , Int > ) -> Unit ,
) {
2022-08-03 00:04:03 +00:00
loadLinks ( result , isVisible = true , isCasting = isCasting ) { links ->
postPopup (
text ,
links . links . map { txt ( " ${it.name} ${Qualities.getStringByInt(it.quality)} " ) } ) {
callback . invoke ( links to ( it ?: return @postPopup ) )
}
2022-08-02 00:43:42 +00:00
}
}
2022-08-03 00:04:03 +00:00
private fun acquireSingleSubtitle (
2022-08-02 00:43:42 +00:00
result : ResultEpisode ,
isCasting : Boolean ,
text : UiText ,
callback : ( Pair < LinkLoadingResult , Int > ) -> Unit ,
) {
2022-08-03 00:04:03 +00:00
loadLinks ( result , isVisible = true , isCasting = isCasting ) { links ->
postPopup (
text ,
links . subs . map { txt ( it . name ) } )
{
callback . invoke ( links to ( it ?: return @postPopup ) )
}
2022-08-02 00:43:42 +00:00
}
}
2022-08-18 00:54:05 +00:00
private suspend fun CoroutineScope . loadLinks (
2022-08-02 00:43:42 +00:00
result : ResultEpisode ,
isVisible : Boolean ,
isCasting : Boolean ,
clearCache : Boolean = false ,
) : LinkLoadingResult {
val tempGenerator = RepoLinkGenerator ( listOf ( result ) )
val links : MutableSet < ExtractorLink > = mutableSetOf ( )
val subs : MutableSet < SubtitleData > = mutableSetOf ( )
fun updatePage ( ) {
2022-08-03 00:04:03 +00:00
if ( isVisible && isActive ) {
_loadedLinks . postValue ( some ( LinkProgress ( links . size , subs . size ) ) )
2022-08-02 00:43:42 +00:00
}
}
try {
2022-08-03 00:04:03 +00:00
updatePage ( )
2022-08-02 00:43:42 +00:00
tempGenerator . generateLinks ( clearCache , isCasting , { ( link , _ ) ->
if ( link != null ) {
links += link
updatePage ( )
}
} , { sub ->
subs += sub
updatePage ( )
} )
} catch ( e : Exception ) {
logError ( e )
} finally {
2022-08-03 00:04:03 +00:00
_loadedLinks . postValue ( Some . None )
2022-08-02 00:43:42 +00:00
}
return LinkLoadingResult ( sortUrls ( links ) , sortSubs ( subs ) )
}
2022-10-08 15:48:46 +00:00
private fun launchActivity (
activity : Activity ? ,
2022-10-08 20:29:17 +00:00
resumeApp : ResultResume ,
id : Int ? = null ,
work : suspend ( Intent . ( Activity ) -> Unit )
2022-10-08 15:48:46 +00:00
) : Job ? {
val act = activity ?: return null
return CoroutineScope ( Dispatchers . IO ) . launch {
try {
2022-10-08 20:29:17 +00:00
resumeApp . launch ( id ) {
work ( act )
}
2022-10-08 15:48:46 +00:00
} catch ( t : Throwable ) {
logError ( t )
main {
if ( t is ActivityNotFoundException ) {
showToast ( activity , txt ( R . string . app _not _found _error ) , Toast . LENGTH _LONG )
} else {
showToast ( activity , t . toString ( ) , Toast . LENGTH _LONG )
}
}
}
}
}
private fun playInWebVideo (
activity : Activity ? ,
link : ExtractorLink ,
title : String ? ,
posterUrl : String ? ,
subtitles : List < SubtitleData >
2022-10-08 20:29:17 +00:00
) = launchActivity ( activity , WEB _VIDEO ) {
setDataAndType ( Uri . parse ( link . url ) , " video/* " )
putExtra ( " subs " , subtitles . map { it . url . toUri ( ) } . toTypedArray ( ) )
title ?. let { putExtra ( " title " , title ) }
posterUrl ?. let { putExtra ( " poster " , posterUrl ) }
2022-10-08 15:48:46 +00:00
val headers = Bundle ( ) . apply {
if ( link . referer . isNotBlank ( ) )
putString ( " Referer " , link . referer )
putString ( " User-Agent " , USER _AGENT )
for ( ( key , value ) in link . headers ) {
putString ( key , value )
}
}
2022-10-08 20:29:17 +00:00
putExtra ( " android.media.intent.extra.HTTP_HEADERS " , headers )
putExtra ( " secure_uri " , true )
}
2022-10-08 15:48:46 +00:00
2022-10-08 20:29:17 +00:00
private fun playWithMpv (
activity : Activity ? ,
id : Int ,
link : ExtractorLink ,
subtitles : List < SubtitleData > ,
resume : Boolean = true ,
) = launchActivity ( activity , MPV , id ) {
putExtra ( " subs " , subtitles . map { it . url . toUri ( ) } . toTypedArray ( ) )
putExtra ( " subs.name " , subtitles . map { it . name } . toTypedArray ( ) )
putExtra ( " subs.filename " , subtitles . map { it . name } . toTypedArray ( ) )
setDataAndType ( Uri . parse ( link . url ) , " video/* " )
component = MPV _COMPONENT
putExtra ( " secure_uri " , true )
putExtra ( " return_result " , true )
val position = getViewPos ( id ) ?. position
if ( resume && position != null )
putExtra ( " position " , position . toInt ( ) )
2022-10-08 15:48:46 +00:00
}
2022-10-08 14:56:05 +00:00
// https://wiki.videolan.org/Android_Player_Intents/
private fun playWithVlc (
2022-10-08 15:48:46 +00:00
activity : Activity ? ,
2022-10-08 14:56:05 +00:00
data : LinkLoadingResult ,
id : Int ,
resume : Boolean = true ,
// if it is only a single link then resume works correctly
2022-10-08 14:58:14 +00:00
singleFile : Boolean ? = null
2022-10-08 20:29:17 +00:00
) = launchActivity ( activity , VLC , id ) { act ->
addFlags ( Intent . FLAG _GRANT _PERSISTABLE _URI _PERMISSION )
addFlags ( Intent . FLAG _GRANT _PREFIX _URI _PERMISSION )
addFlags ( Intent . FLAG _GRANT _READ _URI _PERMISSION )
addFlags ( Intent . FLAG _GRANT _WRITE _URI _PERMISSION )
2022-10-08 15:48:46 +00:00
val outputDir = act . cacheDir
if ( singleFile ?: ( data . links . size == 1 ) ) {
2022-10-08 20:29:17 +00:00
setDataAndType ( data . links . first ( ) . url . toUri ( ) , " video/* " )
2022-10-08 15:48:46 +00:00
} else {
val outputFile = File . createTempFile ( " mirrorlist " , " .m3u8 " , outputDir )
2022-08-02 00:43:42 +00:00
2022-10-08 15:48:46 +00:00
var text = " #EXTM3U "
2022-08-02 00:43:42 +00:00
2022-10-08 15:48:46 +00:00
// With subtitles it doesn't work for no reason :(
2022-10-08 14:56:05 +00:00
// for (sub in data.subs) {
// text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.name}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.name}\",URI=\"${sub.url}\""
// }
2022-10-08 15:48:46 +00:00
for ( link in data . links ) {
text += " \n #EXTINF:, ${link.name} \n ${link.url} "
}
outputFile . writeText ( text )
2022-10-08 20:29:17 +00:00
setDataAndType (
2022-10-08 15:48:46 +00:00
FileProvider . getUriForFile (
act ,
act . applicationContext . packageName + " .provider " ,
outputFile
) , " video/* "
)
}
2022-10-08 14:56:05 +00:00
2022-10-08 15:48:46 +00:00
val position = if ( resume ) {
getViewPos ( id ) ?. position ?: 0L
} else {
1L
}
2022-08-02 00:43:42 +00:00
2022-10-08 20:29:17 +00:00
component = VLC _COMPONENT
putExtra ( " from_start " , ! resume )
putExtra ( " position " , position )
2022-10-08 15:48:46 +00:00
}
2022-08-02 00:43:42 +00:00
2022-10-08 20:29:17 +00:00
2022-10-08 15:48:46 +00:00
fun handleAction ( activity : Activity ? , click : EpisodeClickEvent ) =
viewModelScope . launchSafe {
handleEpisodeClickEvent ( activity , click )
2022-08-02 00:43:42 +00:00
}
2022-10-08 15:48:46 +00:00
data class ExternalApp (
val packageString : String ,
val name : Int ,
val action : Int ,
)
private val apps = listOf (
ExternalApp (
VLC _PACKAGE ,
R . string . player _settings _play _in _vlc ,
ACTION _PLAY _EPISODE _IN _VLC _PLAYER
) , ExternalApp (
WEB _VIDEO _CAST _PACKAGE ,
R . string . player _settings _play _in _web ,
ACTION _PLAY _EPISODE _IN _WEB _VIDEO
2022-10-08 20:29:17 +00:00
) ,
ExternalApp (
MPV _PACKAGE ,
R . string . player _settings _play _in _mpv ,
ACTION _PLAY _EPISODE _IN _MPV
2022-10-08 15:48:46 +00:00
)
)
2022-08-02 00:43:42 +00:00
private suspend fun handleEpisodeClickEvent ( activity : Activity ? , click : EpisodeClickEvent ) {
when ( click . action ) {
ACTION _SHOW _OPTIONS -> {
2022-08-03 01:02:08 +00:00
val options = mutableListOf < Pair < UiText , Int > > ( )
if ( activity ?. isConnectedToChromecast ( ) == true ) {
options . addAll (
listOf (
txt ( R . string . episode _action _chromecast _episode ) to ACTION _CHROME _CAST _EPISODE ,
txt ( R . string . episode _action _chromecast _mirror ) to ACTION _CHROME _CAST _MIRROR ,
)
)
}
options . add ( txt ( R . string . episode _action _play _in _app ) to ACTION _PLAY _EPISODE _IN _PLAYER )
2022-10-08 15:48:46 +00:00
for ( app in apps ) {
if ( activity ?. isAppInstalled ( app . packageString ) == true ) {
options . add (
txt (
R . string . episode _action _play _in _format ,
txt ( app . name )
) to app . action
)
}
2022-08-03 01:02:08 +00:00
}
2022-10-08 15:48:46 +00:00
2022-08-03 01:02:08 +00:00
options . addAll (
listOf (
txt ( R . string . episode _action _play _in _browser ) to ACTION _PLAY _EPISODE _IN _BROWSER ,
txt ( R . string . episode _action _copy _link ) to ACTION _COPY _LINK ,
txt ( R . string . episode _action _auto _download ) to ACTION _DOWNLOAD _EPISODE ,
txt ( R . string . episode _action _download _mirror ) to ACTION _DOWNLOAD _MIRROR ,
txt ( R . string . episode _action _download _subtitle ) to ACTION _DOWNLOAD _EPISODE _SUBTITLE _MIRROR ,
txt ( R . string . episode _action _reload _links ) to ACTION _RELOAD _EPISODE ,
)
)
2022-08-03 00:04:03 +00:00
postPopup (
txt (
activity ?. getNameFull (
click . data . name ,
click . data . episode ,
click . data . season
) ?: " "
) , // TODO FIX
2022-08-03 01:02:08 +00:00
options
2022-08-03 00:04:03 +00:00
) { result ->
handleEpisodeClickEvent (
activity ,
click . copy ( action = result ?: return @postPopup )
)
}
2022-08-02 00:43:42 +00:00
}
ACTION _CLICK _DEFAULT -> {
activity ?. let { ctx ->
if ( ctx . isConnectedToChromecast ( ) ) {
handleEpisodeClickEvent (
activity ,
click . copy ( action = ACTION _CHROME _CAST _EPISODE )
)
} else {
2022-10-05 22:14:42 +00:00
val action = getPlayerAction ( ctx )
2022-08-02 00:43:42 +00:00
handleEpisodeClickEvent (
activity ,
2022-10-05 22:14:42 +00:00
click . copy ( action = action )
2022-08-02 00:43:42 +00:00
)
}
}
}
2022-08-03 01:02:08 +00:00
/ * not implemented , not used
2022-08-02 00:43:42 +00:00
ACTION _DOWNLOAD _EPISODE _SUBTITLE -> {
2022-08-03 01:02:08 +00:00
loadLinks ( click . data , isVisible = false , isCasting = false ) { links ->
downloadSubtitle ( activity , links . subs , )
}
} * /
ACTION _DOWNLOAD _EPISODE _SUBTITLE _MIRROR -> {
2022-08-02 00:43:42 +00:00
val response = currentResponse ?: return
acquireSingleSubtitle (
click . data ,
false ,
txt ( R . string . episode _action _download _subtitle )
) { ( links , index ) ->
downloadSubtitle (
activity ,
links . subs [ index ] ,
getMeta (
click . data ,
response . name ,
response . apiName ,
response . posterUrl ,
response . isMovie ( ) ,
response . type
)
)
2022-08-03 17:27:49 +00:00
showToast (
2022-08-02 00:43:42 +00:00
activity ,
R . string . download _started ,
Toast . LENGTH _SHORT
)
}
}
ACTION _SHOW _TOAST -> {
2022-08-03 17:27:49 +00:00
showToast ( activity , R . string . play _episode _toast , Toast . LENGTH _SHORT )
2022-08-02 00:43:42 +00:00
}
ACTION _DOWNLOAD _EPISODE -> {
val response = currentResponse ?: return
downloadEpisode (
activity ,
click . data ,
response . isMovie ( ) ,
response . name ,
response . type ,
response . posterUrl ,
response . apiName ,
response . getId ( ) ,
response . url
)
}
ACTION _DOWNLOAD _MIRROR -> {
val response = currentResponse ?: return
acquireSingleLink (
click . data ,
false ,
txt ( R . string . episode _action _download _mirror )
) { ( result , index ) ->
2022-08-04 01:19:59 +00:00
ioSafe {
startDownload (
activity ,
click . data ,
response . isMovie ( ) ,
response . name ,
response . type ,
response . posterUrl ,
response . apiName ,
response . getId ( ) ,
response . url ,
listOf ( result . links [ index ] ) ,
result . subs ,
)
}
2022-08-03 17:27:49 +00:00
showToast (
2022-08-02 00:43:42 +00:00
activity ,
R . string . download _started ,
Toast . LENGTH _SHORT
)
}
}
ACTION _RELOAD _EPISODE -> {
2022-08-03 00:04:03 +00:00
ioSafe {
loadLinks (
click . data ,
isVisible = false ,
isCasting = false ,
clearCache = true
)
}
2022-08-02 00:43:42 +00:00
}
ACTION _CHROME _CAST _MIRROR -> {
acquireSingleLink (
click . data ,
2022-08-03 01:02:08 +00:00
isCasting = true ,
2022-08-02 00:43:42 +00:00
txt ( R . string . episode _action _chromecast _mirror )
) { ( result , index ) ->
startChromecast ( activity , click . data , result . links , result . subs , index )
}
}
ACTION _PLAY _EPISODE _IN _BROWSER -> acquireSingleLink (
click . data ,
2022-08-03 01:02:08 +00:00
isCasting = true ,
2022-08-02 00:43:42 +00:00
txt ( R . string . episode _action _play _in _browser )
) { ( result , index ) ->
try {
val i = Intent ( Intent . ACTION _VIEW )
i . data = Uri . parse ( result . links [ index ] . url )
activity ?. startActivity ( i )
} catch ( e : Exception ) {
logError ( e )
}
}
ACTION _COPY _LINK -> {
acquireSingleLink (
click . data ,
2022-08-03 01:02:08 +00:00
isCasting = true ,
2022-08-02 00:43:42 +00:00
txt ( R . string . episode _action _copy _link )
) { ( result , index ) ->
val act = activity ?: return @acquireSingleLink
val serviceClipboard =
( act . getSystemService ( Context . CLIPBOARD _SERVICE ) as ? ClipboardManager ? )
?: return @acquireSingleLink
val link = result . links [ index ]
val clip = ClipData . newPlainText ( link . name , link . url )
serviceClipboard . setPrimaryClip ( clip )
2022-08-03 17:27:49 +00:00
showToast ( act , R . string . copy _link _toast , Toast . LENGTH _SHORT )
2022-08-02 00:43:42 +00:00
}
}
ACTION _CHROME _CAST _EPISODE -> {
startChromecast ( activity , click . data )
}
ACTION _PLAY _EPISODE _IN _VLC _PLAYER -> {
2022-08-03 00:04:03 +00:00
loadLinks ( click . data , isVisible = true , isCasting = true ) { links ->
2022-10-08 15:48:46 +00:00
if ( links . links . isEmpty ( ) ) {
showToast ( activity , R . string . no _links _found _toast , Toast . LENGTH _SHORT )
return @loadLinks
}
2022-08-03 00:04:03 +00:00
playWithVlc (
activity ,
links ,
click . data . id
)
2022-08-02 00:43:42 +00:00
}
}
2022-10-08 15:48:46 +00:00
ACTION _PLAY _EPISODE _IN _WEB _VIDEO -> acquireSingleLink (
click . data ,
isCasting = true ,
txt (
R . string . episode _action _play _in _format ,
txt ( R . string . player _settings _play _in _web )
)
) { ( result , index ) ->
playInWebVideo (
activity ,
result . links [ index ] ,
click . data . name ?: click . data . headerName ,
click . data . poster ,
result . subs
)
}
2022-10-08 20:29:17 +00:00
ACTION _PLAY _EPISODE _IN _MPV -> acquireSingleLink (
click . data ,
isCasting = true ,
txt (
R . string . episode _action _play _in _format ,
txt ( R . string . player _settings _play _in _mpv )
)
) { ( result , index ) ->
playWithMpv (
activity ,
click . data . id ,
result . links [ index ] ,
result . subs
)
}
2022-08-02 00:43:42 +00:00
ACTION _PLAY _EPISODE _IN _PLAYER -> {
val data = currentResponse ?. syncData ?. toList ( ) ?: emptyList ( )
val list =
HashMap < String , String > ( ) . apply { putAll ( data ) }
activity ?. navigate (
R . id . global _to _navigation _player ,
GeneratorPlayer . newInstance (
generator ?. also {
it . getAll ( ) // I know kinda shit to itterate all, but it is 100% sure to work
?. indexOfFirst { value -> value is ResultEpisode && value . id == click . data . id }
?. let { index ->
2022-08-31 18:08:45 +00:00
if ( index >= 0 )
2022-08-02 00:43:42 +00:00
it . goto ( index )
}
} ?: return , list
)
)
}
}
2022-08-01 01:00:48 +00:00
}
2022-08-01 02:46:43 +00:00
private suspend fun applyMeta (
resp : LoadResponse ,
meta : SyncAPI . SyncResult ? ,
syncs : Map < String , String > ? = null
) : Pair < LoadResponse , Boolean > {
if ( meta == null ) return resp to false
var updateEpisodes = false
val out = resp . apply {
2022-08-02 00:43:42 +00:00
Log . i ( TAG , " applyMeta " )
2022-08-01 02:46:43 +00:00
duration = duration ?: meta . duration
rating = rating ?: meta . publicScore
tags = tags ?: meta . genres
plot = if ( plot . isNullOrBlank ( ) ) meta . synopsis else plot
posterUrl = posterUrl ?: meta . posterUrl ?: meta . backgroundPosterUrl
actors = actors ?: meta . actors
if ( this is EpisodeResponse ) {
nextAiring = nextAiring ?: meta . nextAiring
}
for ( ( k , v ) in syncs ?: emptyMap ( ) ) {
syncData [ k ] = v
}
val realRecommendations = ArrayList < SearchResponse > ( )
2022-08-07 08:46:58 +00:00
// TODO: fix
//val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name)
// meta.recommendations?.forEach { rec ->
// apiNames.forEach { name ->
// realRecommendations.add(rec.copy(apiName = name))
// }
// }
2022-08-01 02:46:43 +00:00
recommendations = recommendations ?. union ( realRecommendations ) ?. toList ( )
?: realRecommendations
argamap ( {
addTrailer ( meta . trailers )
} , {
if ( this !is AnimeLoadResponse ) return @argamap
val map =
2022-10-08 15:48:46 +00:00
Kitsu . getEpisodesDetails (
getMalId ( ) ,
getAniListId ( ) ,
isResponseRequired = false
)
2022-08-01 02:46:43 +00:00
if ( map . isNullOrEmpty ( ) ) return @argamap
updateEpisodes = DubStatus . values ( ) . map { dubStatus ->
val current =
this . episodes [ dubStatus ] ?. mapIndexed { index , episode ->
episode . apply {
this . episode = this . episode ?: ( index + 1 )
}
} ?. sortedBy { it . episode ?: 0 } ?. toMutableList ( )
if ( current . isNullOrEmpty ( ) ) return @map false
val episodeNumbers = current . map { ep -> ep . episode !! }
var updateCount = 0
map . forEach { ( episode , node ) ->
episodeNumbers . binarySearch ( episode ) . let { index ->
current . getOrNull ( index ) ?. let { currentEp ->
current [ index ] = currentEp . apply {
updateCount ++
val currentBack = this
this . description = this . description ?: node . description ?. en
this . name = this . name ?: node . titles ?. canonical
2022-10-08 15:48:46 +00:00
this . episode =
this . episode ?: node . num ?: episodeNumbers [ index ]
this . posterUrl =
this . posterUrl ?: node . thumbnail ?. original ?. url
2022-08-01 02:46:43 +00:00
}
}
}
}
this . episodes [ dubStatus ] = current
updateCount > 0
} . any { it }
} )
}
return out to updateEpisodes
}
2022-08-03 01:02:08 +00:00
fun setMeta ( meta : SyncAPI . SyncResult , syncs : Map < String , String > ? ) {
// I dont want to update everything if the metadata is not relevant
if ( currentMeta == meta && currentSync == syncs ) {
Log . i ( TAG , " setMeta same " )
return
}
Log . i ( TAG , " setMeta " )
2022-08-18 00:54:05 +00:00
viewModelScope . launchSafe {
2022-08-01 02:46:43 +00:00
currentMeta = meta
currentSync = syncs
2022-08-04 01:19:59 +00:00
val ( value , updateEpisodes ) = ioWork {
2022-08-01 02:46:43 +00:00
currentResponse ?. let { resp ->
return @ioWork applyMeta ( resp , meta , syncs )
}
return @ioWork null to null
}
postSuccessful (
2022-08-18 00:54:05 +00:00
value ?: return @launchSafe ,
currentRepo ?: return @launchSafe ,
updateEpisodes ?: return @launchSafe ,
2022-08-01 02:46:43 +00:00
false
)
}
2022-08-03 01:02:08 +00:00
}
2022-08-01 02:46:43 +00:00
2022-08-01 01:00:48 +00:00
private suspend fun updateFillers ( name : String ) {
fillers =
2022-08-18 00:54:05 +00:00
ioWorkSafe {
FillerEpisodeCheck . getFillerEpisodes ( name )
2022-08-01 01:00:48 +00:00
} ?: emptyMap ( )
}
fun changeDubStatus ( status : DubStatus ) {
postEpisodeRange ( currentIndex ?. copy ( dubStatus = status ) , currentRange )
}
fun changeRange ( range : EpisodeRange ) {
postEpisodeRange ( currentIndex , range )
}
2022-08-01 02:46:43 +00:00
fun changeSeason ( season : Int ) {
postEpisodeRange ( currentIndex ?. copy ( season = season ) , currentRange )
}
2022-08-03 00:04:03 +00:00
private fun getMovie ( ) : ResultEpisode ? {
return currentEpisodes . entries . firstOrNull ( ) ?. value ?. firstOrNull ( ) ?. let { ep ->
2022-08-03 17:27:49 +00:00
val posDur = getViewPos ( ep . id )
2022-08-03 00:04:03 +00:00
ep . copy ( position = posDur ?. position ?: 0 , duration = posDur ?. duration ?: 0 )
}
}
2022-08-01 01:00:48 +00:00
private fun getEpisodes ( indexer : EpisodeIndexer , range : EpisodeRange ) : List < ResultEpisode > {
val startIndex = range . startIndex
val length = range . length
return currentEpisodes [ indexer ]
?. let { list ->
val start = minOf ( list . size , startIndex )
val end = minOf ( list . size , start + length )
list . subList ( start , end ) . map {
2022-08-03 17:27:49 +00:00
val posDur = getViewPos ( it . id )
2022-08-01 01:00:48 +00:00
it . copy ( position = posDur ?. position ?: 0 , duration = posDur ?. duration ?: 0 )
}
}
?: emptyList ( )
}
2022-08-03 00:04:03 +00:00
private fun postMovie ( ) {
val response = currentResponse
_episodes . postValue ( ResourceSome . None )
if ( response == null ) {
_movie . postValue ( ResourceSome . None )
return
}
val text = txt (
when ( response . type ) {
TvType . Torrent -> R . string . play _torrent _button
else -> {
if ( response . type . isLiveStream ( ) )
R . string . play _livestream _button
else if ( response . type . isMovieType ( ) ) // this wont break compatibility as you only need to override isMovieType
R . string . play _movie _button
else null
}
}
)
val data = getMovie ( )
_episodes . postValue ( ResourceSome . None )
if ( text == null || data == null ) {
_movie . postValue ( ResourceSome . None )
} else {
_movie . postValue ( ResourceSome . Success ( text to data ) )
}
}
2022-08-01 01:00:48 +00:00
fun reloadEpisodes ( ) {
2022-08-03 00:04:03 +00:00
if ( currentResponse ?. isMovie ( ) == true ) {
postMovie ( )
} else {
_episodes . postValue (
ResourceSome . Success (
getEpisodes (
currentIndex ?: return ,
currentRange ?: return
)
2022-08-01 01:00:48 +00:00
)
)
2022-08-03 00:04:03 +00:00
_movie . postValue ( ResourceSome . None )
}
2022-08-03 17:27:49 +00:00
postResume ( )
2022-08-01 01:00:48 +00:00
}
private fun postEpisodeRange ( indexer : EpisodeIndexer ? , range : EpisodeRange ? ) {
if ( range == null || indexer == null ) {
return
}
2022-08-03 00:04:03 +00:00
val episodes = currentEpisodes [ indexer ]
val ranges = currentRanges [ indexer ]
2022-08-05 23:41:35 +00:00
if ( ranges ?. contains ( range ) != true ) {
// if the current ranges does not include the range then select the range with the closest matching start episode
// this usually happends when dub has less episodes then sub -> the range does not exist
ranges ?. minByOrNull { abs ( it . startEpisode - range . startEpisode ) } ?. let { r ->
postEpisodeRange ( indexer , r )
return
}
}
2022-08-03 00:04:03 +00:00
val size = episodes ?. size
val isMovie = currentResponse ?. isMovie ( ) == true
currentIndex = indexer
currentRange = range
_rangeSelections . postValue ( ranges ?. map { r ->
val text = txt ( R . string . episodes _range , r . startEpisode , r . endEpisode )
text to r
} ?: emptyList ( ) )
2022-08-01 02:46:43 +00:00
_episodesCountText . postValue (
2022-08-03 00:04:03 +00:00
some (
if ( isMovie ) null else
txt (
R . string . episode _format ,
size ,
txt ( if ( size == 1 ) R . string . episode else R . string . episodes ) ,
)
2022-08-01 02:46:43 +00:00
)
)
2022-08-05 23:41:35 +00:00
_selectedSeasonIndex . postValue (
currentSeasons . indexOf ( indexer . season )
)
2022-08-01 02:46:43 +00:00
_selectedSeason . postValue (
2022-08-03 00:04:03 +00:00
some (
if ( isMovie || currentSeasons . size <= 1 ) null else
when ( indexer . season ) {
0 -> txt ( R . string . no _season )
2022-09-10 17:59:37 +00:00
else -> {
val seasonNames = ( currentResponse as ? EpisodeResponse ) ?. seasonNames
2022-09-12 14:00:27 +00:00
val seasonData = seasonNames . getSeason ( indexer . season )
// If displaySeason is null then only show the name!
if ( seasonData ?. name != null && seasonData . displaySeason == null ) {
txt ( seasonData . name )
} else {
val suffix = seasonData ?. name ?. let { " $it " } ?: " "
txt (
R . string . season _format ,
txt ( R . string . season ) ,
seasonData ?. displaySeason ?: indexer . season ,
suffix
)
}
2022-09-10 17:59:37 +00:00
}
2022-08-03 00:04:03 +00:00
}
)
2022-08-01 02:46:43 +00:00
)
2022-08-03 00:04:03 +00:00
2022-08-05 23:41:35 +00:00
_selectedRangeIndex . postValue (
ranges ?. indexOf ( range ) ?: - 1
)
2022-08-01 02:46:43 +00:00
_selectedRange . postValue (
2022-08-03 00:04:03 +00:00
some (
if ( isMovie ) null else if ( ( currentRanges [ indexer ] ?. size ?: 0 ) > 1 ) {
txt ( R . string . episodes _range , range . startEpisode , range . endEpisode )
} else {
null
}
)
)
2022-08-05 23:41:35 +00:00
_selectedDubStatusIndex . postValue (
currentDubStatus . indexOf ( indexer . dubStatus )
)
2022-08-03 00:04:03 +00:00
_selectedDubStatus . postValue (
some (
if ( isMovie || currentDubStatus . size <= 1 ) null else
txt ( indexer . dubStatus )
)
2022-08-01 02:46:43 +00:00
)
2022-08-04 01:19:59 +00:00
currentId ?. let { id ->
setDub ( id , indexer . dubStatus )
setResultSeason ( id , indexer . season )
setResultEpisode ( id , range . startEpisode )
}
2022-08-01 01:00:48 +00:00
preferStartEpisode = range . startEpisode
preferStartSeason = indexer . season
preferDubStatus = indexer . dubStatus
2022-08-03 00:04:03 +00:00
generator = if ( isMovie ) {
getMovie ( ) ?. let { RepoLinkGenerator ( listOf ( it ) ) }
} else {
episodes ?. let { list ->
RepoLinkGenerator ( list )
}
2022-08-02 00:43:42 +00:00
}
2022-08-03 00:04:03 +00:00
if ( isMovie ) {
postMovie ( )
} else {
val ret = getEpisodes ( indexer , range )
2022-08-05 23:41:35 +00:00
/ * if ( ret . isEmpty ( ) ) {
val index = ranges ?. indexOf ( range )
if ( index != null && index > 0 ) {
}
} * /
2022-08-03 00:04:03 +00:00
_episodes . postValue ( ResourceSome . Success ( ret ) )
}
2022-08-01 01:00:48 +00:00
}
private suspend fun postSuccessful (
loadResponse : LoadResponse ,
apiRepository : APIRepository ,
updateEpisodes : Boolean ,
updateFillers : Boolean ,
) {
currentResponse = loadResponse
postPage ( loadResponse , apiRepository )
if ( updateEpisodes )
postEpisodes ( loadResponse , updateFillers )
}
private suspend fun postEpisodes ( loadResponse : LoadResponse , updateFillers : Boolean ) {
2022-08-03 00:04:03 +00:00
_episodes . postValue ( ResourceSome . Loading ( ) )
2022-08-01 01:00:48 +00:00
val mainId = loadResponse . getId ( )
currentId = mainId
2022-08-03 00:04:03 +00:00
_watchStatus . postValue ( getResultWatchState ( mainId ) )
2022-08-01 01:00:48 +00:00
if ( updateFillers && loadResponse is AnimeLoadResponse ) {
updateFillers ( loadResponse . name )
}
val allEpisodes = when ( loadResponse ) {
is AnimeLoadResponse -> {
val existingEpisodes = HashSet < Int > ( )
val episodes : MutableMap < EpisodeIndexer , MutableList < ResultEpisode > > =
mutableMapOf ( )
loadResponse . episodes . map { ep ->
val idIndex = ep . key . id
for ( ( index , i ) in ep . value . withIndex ( ) ) {
val episode = i . episode ?: ( index + 1 )
2022-10-05 22:14:42 +00:00
val id =
2022-10-08 15:48:46 +00:00
mainId + episode + idIndex * 1 _000 _000 + ( i . season ?. times ( 10 _000 )
?: 0 )
2022-09-12 15:22:48 +00:00
if ( ! existingEpisodes . contains ( id ) ) {
2022-08-01 01:00:48 +00:00
existingEpisodes . add ( id )
2022-09-10 17:59:37 +00:00
val seasonData = loadResponse . seasonNames . getSeason ( i . season )
2022-08-01 01:00:48 +00:00
val eps =
buildResultEpisode (
loadResponse . name ,
filterName ( i . name ) ,
i . posterUrl ,
episode ,
2022-09-12 15:22:48 +00:00
seasonData ?. season ?: i . season ,
2022-09-12 14:00:27 +00:00
if ( seasonData != null ) seasonData . displaySeason else i . season ,
2022-08-01 01:00:48 +00:00
i . data ,
loadResponse . apiName ,
id ,
index ,
i . rating ,
i . description ,
fillers . getOrDefault ( episode , false ) ,
loadResponse . type ,
mainId
)
2022-09-12 15:22:48 +00:00
val season = eps . seasonIndex ?: 0
2022-08-01 01:00:48 +00:00
val indexer = EpisodeIndexer ( ep . key , season )
episodes [ indexer ] ?. add ( eps ) ?: run {
episodes [ indexer ] = mutableListOf ( eps )
}
}
}
}
episodes
}
is TvSeriesLoadResponse -> {
val episodes : MutableMap < EpisodeIndexer , MutableList < ResultEpisode > > =
mutableMapOf ( )
val existingEpisodes = HashSet < Int > ( )
for ( ( index , episode ) in loadResponse . episodes . sortedBy {
2022-09-12 15:22:48 +00:00
( it . season ?. times ( 10 _000 ) ?: 0 ) + ( it . episode ?: 0 )
2022-08-01 01:00:48 +00:00
} . withIndex ( ) ) {
val episodeIndex = episode . episode ?: ( index + 1 )
val id =
2022-09-12 15:22:48 +00:00
mainId + ( episode . season ?. times ( 100 _000 ) ?: 0 ) + episodeIndex + 1
2022-08-01 01:00:48 +00:00
if ( ! existingEpisodes . contains ( id ) ) {
existingEpisodes . add ( id )
2022-09-12 14:00:27 +00:00
val seasonData =
2022-09-10 17:59:37 +00:00
loadResponse . seasonNames . getSeason ( episode . season )
2022-08-01 01:00:48 +00:00
val ep =
buildResultEpisode (
loadResponse . name ,
filterName ( episode . name ) ,
episode . posterUrl ,
episodeIndex ,
2022-09-12 15:22:48 +00:00
seasonData ?. season ?: episode . season ,
2022-09-12 14:00:27 +00:00
if ( seasonData != null ) seasonData . displaySeason else episode . season ,
2022-08-01 01:00:48 +00:00
episode . data ,
loadResponse . apiName ,
id ,
index ,
episode . rating ,
episode . description ,
null ,
loadResponse . type ,
mainId
)
2022-09-12 15:22:48 +00:00
val season = ep . seasonIndex ?: 0
2022-08-01 01:00:48 +00:00
val indexer = EpisodeIndexer ( DubStatus . None , season )
episodes [ indexer ] ?. add ( ep ) ?: kotlin . run {
episodes [ indexer ] = mutableListOf ( ep )
}
}
}
episodes
}
is MovieLoadResponse -> {
singleMap (
buildResultEpisode (
loadResponse . name ,
loadResponse . name ,
null ,
0 ,
null ,
null ,
loadResponse . dataUrl ,
loadResponse . apiName ,
( mainId ) , // HAS SAME ID
0 ,
null ,
null ,
null ,
loadResponse . type ,
mainId
)
)
}
is LiveStreamLoadResponse -> {
singleMap (
buildResultEpisode (
loadResponse . name ,
loadResponse . name ,
null ,
0 ,
null ,
null ,
loadResponse . dataUrl ,
loadResponse . apiName ,
( mainId ) , // HAS SAME ID
0 ,
null ,
null ,
null ,
loadResponse . type ,
mainId
)
)
}
is TorrentLoadResponse -> {
singleMap (
buildResultEpisode (
loadResponse . name ,
loadResponse . name ,
null ,
0 ,
null ,
null ,
loadResponse . torrent ?: loadResponse . magnet ?: " " ,
loadResponse . apiName ,
( mainId ) , // HAS SAME ID
0 ,
null ,
null ,
null ,
loadResponse . type ,
mainId
)
)
}
else -> {
mapOf ( )
}
}
2022-08-03 00:04:03 +00:00
val seasonsSelection = mutableSetOf < Int > ( )
val dubSelection = mutableSetOf < DubStatus > ( )
allEpisodes . keys . forEach { key ->
seasonsSelection += key . season
dubSelection += key . dubStatus
}
2022-08-05 23:41:35 +00:00
currentDubStatus = dubSelection . toList ( )
currentSeasons = seasonsSelection . toList ( )
2022-08-03 00:04:03 +00:00
_dubSubSelections . postValue ( dubSelection . map { txt ( it ) to it } )
if ( loadResponse is EpisodeResponse ) {
_seasonSelections . postValue ( seasonsSelection . map { seasonNumber ->
2022-09-10 17:59:37 +00:00
val seasonData = loadResponse . seasonNames . getSeason ( seasonNumber )
val fixedSeasonNumber = seasonData ?. displaySeason ?: seasonNumber
val suffix = seasonData ?. name ?. let { " $it " } ?: " "
2022-09-12 14:00:27 +00:00
// If displaySeason is null then only show the name!
val name = if ( seasonData ?. name != null && seasonData . displaySeason == null ) {
txt ( seasonData . name )
} else {
txt (
R . string . season _format ,
txt ( R . string . season ) ,
fixedSeasonNumber ,
suffix
)
}
2022-08-03 00:04:03 +00:00
name to seasonNumber
} )
}
2022-08-01 01:00:48 +00:00
currentEpisodes = allEpisodes
val ranges = getRanges ( allEpisodes )
currentRanges = ranges
2022-08-03 00:04:03 +00:00
2022-08-01 01:00:48 +00:00
// this takes the indexer most preferable by the user given the current sorting
val min = ranges . keys . minByOrNull { index ->
kotlin . math . abs (
index . season - ( preferStartSeason ?: 0 )
) + if ( index . dubStatus == preferDubStatus ) 0 else 100000
}
// this takes the range most preferable by the user given the current sorting
val ranger = ranges [ min ]
val range = ranger ?. firstOrNull {
it . startEpisode >= ( preferStartEpisode ?: 0 )
} ?: ranger ?. lastOrNull ( )
postEpisodeRange ( min , range )
2022-08-03 17:27:49 +00:00
postResume ( )
}
fun postResume ( ) {
_resumeWatching . postValue ( some ( resume ( ) ) )
2022-08-01 01:00:48 +00:00
}
2022-08-03 17:27:49 +00:00
private fun resume ( ) : ResumeWatchingStatus ? {
val correctId = currentId ?: return null
val resume = DataStoreHelper . getLastWatched ( correctId )
val resumeParentId = resume ?. parentId
if ( resumeParentId != correctId ) return null // is null or smth went wrong with getLastWatched
val resumeId = resume . episodeId ?: return null // invalid episode id
val response = currentResponse ?: return null
// kinda ugly ik
val episode =
currentEpisodes . values . flatten ( ) . firstOrNull { it . id == resumeId } ?: return null
val isMovie = response . isMovie ( )
val progress = getViewPos ( resume . episodeId ) ?. let { viewPos ->
ResumeProgress (
progress = ( viewPos . position / 1000 ) . toInt ( ) ,
maxProgress = ( viewPos . duration / 1000 ) . toInt ( ) ,
txt ( R . string . resume _time _left , ( viewPos . duration - viewPos . position ) / ( 60 _000 ) )
)
}
return ResumeWatchingStatus ( progress = progress , isMovie = isMovie , result = episode )
}
2022-08-25 01:59:20 +00:00
private fun loadTrailers ( loadResponse : LoadResponse ) = ioSafe {
2022-09-12 14:00:27 +00:00
_trailers . postValue (
getTrailers (
loadResponse ,
3
)
) // we dont want to fetch too many trailers
2022-08-25 01:59:20 +00:00
}
private suspend fun getTrailers (
loadResponse : LoadResponse ,
limit : Int = 0
) : List < ExtractedTrailerData > =
coroutineScope {
var currentCount = 0
return @coroutineScope loadResponse . trailers . apmap { trailerData ->
try {
val links = arrayListOf < ExtractorLink > ( )
val subs = arrayListOf < SubtitleFile > ( )
if ( ! loadExtractor (
trailerData . extractorUrl ,
trailerData . referer ,
{ subs . add ( it ) } ,
{ links . add ( it ) } ) && trailerData . raw
) {
arrayListOf (
ExtractorLink (
" " ,
" Trailer " ,
trailerData . extractorUrl ,
trailerData . referer ?: " " ,
Qualities . Unknown . value ,
trailerData . extractorUrl . contains ( " .m3u8 " )
)
) to arrayListOf ( )
} else {
links to subs
} . also { ( extractor , _ ) ->
if ( extractor . isNotEmpty ( ) && limit != 0 ) {
currentCount ++
if ( currentCount >= limit ) {
cancel ( )
}
}
}
} catch ( e : Throwable ) {
logError ( e )
null
}
} . filterNotNull ( ) . map { ( links , subs ) -> ExtractedTrailerData ( links , subs ) }
}
2022-08-03 17:27:49 +00:00
2022-08-01 01:00:48 +00:00
// this instantly updates the metadata on the page
private fun postPage ( loadResponse : LoadResponse , apiRepository : APIRepository ) {
2022-08-01 02:46:43 +00:00
_recommendations . postValue ( loadResponse . recommendations ?: emptyList ( ) )
2022-08-01 01:00:48 +00:00
_page . postValue ( Resource . Success ( loadResponse . toResultData ( apiRepository ) ) )
}
2022-08-01 02:46:43 +00:00
fun hasLoaded ( ) = currentResponse != null
2022-08-04 01:19:59 +00:00
private fun handleAutoStart ( activity : Activity ? , autostart : AutoResume ? ) =
2022-08-18 00:54:05 +00:00
viewModelScope . launchSafe {
if ( autostart == null || activity == null ) return @launchSafe
2022-08-04 01:19:59 +00:00
when ( autostart . startAction ) {
START _ACTION _RESUME _LATEST -> {
currentEpisodes [ currentIndex ] ?. let { currentRange ->
for ( ep in currentRange ) {
if ( ep . getWatchProgress ( ) > 0.9 ) continue
handleAction (
activity ,
2022-10-05 22:14:42 +00:00
EpisodeClickEvent (
getPlayerAction ( activity ) ,
ep
)
2022-08-04 01:19:59 +00:00
)
break
}
}
}
START _ACTION _LOAD _EP -> {
val all = currentEpisodes . values . flatten ( )
val episode =
autostart . id ?. let { id -> all . firstOrNull { it . id == id } }
?: autostart . episode ?. let { ep ->
currentEpisodes [ currentIndex ] ?. firstOrNull { it . episode == ep && it . season == autostart . episode }
?: all . firstOrNull { it . episode == ep && it . season == autostart . episode }
}
2022-08-18 00:54:05 +00:00
?: return @launchSafe
2022-08-04 01:19:59 +00:00
handleAction (
activity ,
2022-10-05 22:14:42 +00:00
EpisodeClickEvent (
getPlayerAction ( activity ) ,
episode
)
2022-08-04 01:19:59 +00:00
)
}
}
}
2022-08-01 01:00:48 +00:00
fun load (
2022-08-04 01:19:59 +00:00
activity : Activity ? ,
2022-08-01 01:00:48 +00:00
url : String ,
apiName : String ,
showFillers : Boolean ,
dubStatus : DubStatus ,
2022-08-04 01:19:59 +00:00
autostart : AutoResume ? ,
2022-08-01 01:00:48 +00:00
) =
2022-08-18 00:54:05 +00:00
viewModelScope . launchSafe {
2022-08-01 01:00:48 +00:00
_page . postValue ( Resource . Loading ( url ) )
2022-08-03 00:04:03 +00:00
_episodes . postValue ( ResourceSome . Loading ( ) )
2022-08-01 01:00:48 +00:00
preferDubStatus = dubStatus
currentShowFillers = showFillers
// set api
val api = APIHolder . getApiFromNameNull ( apiName ) ?: APIHolder . getApiFromUrlNull ( url )
if ( api == null ) {
_page . postValue (
Resource . Failure (
false ,
null ,
null ,
" This provider does not exist "
)
)
2022-08-18 00:54:05 +00:00
return @launchSafe
2022-08-01 01:00:48 +00:00
}
// validate url
2022-08-18 00:54:05 +00:00
val validUrlResource = safeApiCall {
2022-08-01 01:00:48 +00:00
SyncRedirector . redirect (
url ,
2022-08-07 08:46:58 +00:00
api . mainUrl
2022-08-01 01:00:48 +00:00
)
}
2022-08-07 08:46:58 +00:00
// TODO: fix
// val validUrlResource = safeApiCall {
// SyncRedirector.redirect(
// url,
// api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime")
// .replace(GogoanimeProvider().mainUrl, "gogoanime")
// )
// }
2022-08-01 01:00:48 +00:00
if ( validUrlResource !is Resource . Success ) {
if ( validUrlResource is Resource . Failure ) {
_page . postValue ( validUrlResource )
}
2022-08-18 00:54:05 +00:00
return @launchSafe
2022-08-01 01:00:48 +00:00
}
val validUrl = validUrlResource . value
val repo = APIRepository ( api )
currentRepo = repo
when ( val data = repo . load ( validUrl ) ) {
is Resource . Failure -> {
_page . postValue ( data )
}
is Resource . Success -> {
2022-08-18 00:54:05 +00:00
if ( !is Active ) return @launchSafe
2022-08-04 01:19:59 +00:00
val loadResponse = ioWork {
2022-08-01 02:46:43 +00:00
applyMeta ( data . value , currentMeta , currentSync ) . first
}
2022-08-18 00:54:05 +00:00
if ( !is Active ) return @launchSafe
2022-08-01 01:00:48 +00:00
val mainId = loadResponse . getId ( )
2022-08-04 01:19:59 +00:00
preferDubStatus = getDub ( mainId ) ?: preferDubStatus
preferStartEpisode = getResultEpisode ( mainId )
preferStartSeason = getResultSeason ( mainId )
2022-10-08 20:29:17 +00:00
setKey (
2022-08-01 01:00:48 +00:00
DOWNLOAD _HEADER _CACHE ,
mainId . toString ( ) ,
VideoDownloadHelper . DownloadHeaderCached (
apiName ,
validUrl ,
loadResponse . type ,
loadResponse . name ,
loadResponse . posterUrl ,
mainId ,
System . currentTimeMillis ( ) ,
)
)
2022-08-25 01:59:20 +00:00
loadTrailers ( data . value )
2022-08-01 01:00:48 +00:00
postSuccessful (
data . value ,
updateEpisodes = true ,
updateFillers = showFillers ,
apiRepository = repo
)
2022-08-18 00:54:05 +00:00
if ( !is Active ) return @launchSafe
2022-08-04 01:19:59 +00:00
handleAutoStart ( activity , autostart )
2022-08-01 01:00:48 +00:00
}
is Resource . Loading -> {
debugException { " Invalid load result " }
}
}
}
}