2021-02-06 19:02:12 +01:00
package org.mosad.teapod.ui.activity.player
2020-11-25 16:04:04 +01:00
2020-12-26 20:09:35 +01:00
import android.app.Application
import android.net.Uri
2021-06-12 20:57:12 +02:00
import android.support.v4.media.session.MediaSessionCompat
2020-12-27 20:31:18 +01:00
import android.util.Log
2020-12-26 20:09:35 +01:00
import androidx.lifecycle.AndroidViewModel
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.SimpleExoPlayer
2021-06-12 20:57:12 +02:00
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
2020-12-26 20:09:35 +01:00
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
2020-11-27 11:06:16 +01:00
import kotlinx.coroutines.runBlocking
2020-12-27 20:11:01 +01:00
import org.mosad.teapod.R
2021-12-20 22:14:58 +01:00
import org.mosad.teapod.parser.crunchyroll.Crunchyroll
import org.mosad.teapod.parser.crunchyroll.NoneEpisode
import org.mosad.teapod.parser.crunchyroll.NoneEpisodes
import org.mosad.teapod.parser.crunchyroll.NonePlayback
2020-12-15 23:15:14 +01:00
import org.mosad.teapod.preferences.Preferences
2021-12-26 20:22:00 +01:00
import org.mosad.teapod.util.AoDEpisodeNone
import org.mosad.teapod.util.EpisodeMeta
import org.mosad.teapod.util.Meta
import org.mosad.teapod.util.TVShowMeta
2021-09-05 00:08:03 +02:00
import org.mosad.teapod.util.tmdb.TMDBTVSeason
2020-12-26 14:39:35 +01:00
import java.util.*
import kotlin.collections.ArrayList
2020-11-25 16:04:04 +01:00
2020-12-15 23:15:14 +01:00
/ * *
* PlayerViewModel handles all stuff related to media / episodes .
* When currentEpisode is changed the player will start playing it ( not initial media ) ,
* the next episode will be update and the callback is handled .
* /
2020-12-26 20:09:35 +01:00
class PlayerViewModel ( application : Application ) : AndroidViewModel ( application ) {
val player = SimpleExoPlayer . Builder ( application ) . build ( )
2021-06-12 20:57:12 +02:00
private val dataSourceFactory = DefaultDataSourceFactory ( application , Util . getUserAgent ( application , " Teapod " ) )
private val mediaSession = MediaSessionCompat ( application , " TEAPOD_PLAYER_SESSION " )
2020-12-15 23:15:14 +01:00
val currentEpisodeChangedListener = ArrayList < ( ) -> Unit > ( )
2021-06-12 20:57:12 +02:00
private val preferredLanguage = if ( Preferences . preferSecondary ) Locale . JAPANESE else Locale . GERMAN
2020-12-15 23:15:14 +01:00
2021-12-26 20:22:00 +01:00
// var media: AoDMedia = AoDMediaNone
// internal set
2021-09-05 11:54:55 +02:00
var mediaMeta : Meta ? = null
2020-11-25 16:04:04 +01:00
internal set
2021-09-05 00:08:03 +02:00
var tmdbTVSeason : TMDBTVSeason ? = null
internal set
2021-09-05 11:54:55 +02:00
var currentEpisode = AoDEpisodeNone
2021-07-11 12:56:21 +02:00
internal set
2021-07-17 19:40:16 +02:00
var currentEpisodeMeta : EpisodeMeta ? = null
internal set
2021-12-26 20:22:00 +01:00
// var nextEpisodeId: Int? = null
// internal set
2020-12-27 20:11:01 +01:00
var currentLanguage : Locale = Locale . ROOT
internal set
2020-11-25 16:04:04 +01:00
2021-12-20 22:14:58 +01:00
var episodesCrunchy = NoneEpisodes
internal set
var currentEpisodeCr = NoneEpisode
internal set
2021-12-26 20:22:00 +01:00
private var currentPlaybackCr = NonePlayback
2021-12-20 22:14:58 +01:00
2021-06-12 20:57:12 +02:00
init {
initMediaSession ( )
}
2020-12-27 20:31:18 +01:00
override fun onCleared ( ) {
super . onCleared ( )
2021-06-12 20:57:12 +02:00
mediaSession . release ( )
2020-12-27 20:31:18 +01:00
player . release ( )
Log . d ( javaClass . name , " Released player " )
}
2021-06-12 20:57:12 +02:00
/ * *
* set the media session to active
* create a media session connector to set title and description
* /
private fun initMediaSession ( ) {
val mediaSessionConnector = MediaSessionConnector ( mediaSession )
mediaSessionConnector . setPlayer ( player )
mediaSession . isActive = true
}
2021-12-20 22:14:58 +01:00
fun loadMedia ( seasonId : String , episodeId : String ) {
2020-11-27 11:06:16 +01:00
runBlocking {
2021-12-20 22:14:58 +01:00
episodesCrunchy = Crunchyroll . episodes ( seasonId )
//mediaMeta = loadMediaMeta(media.aodId) // can be done blocking, since it should be cached
2021-12-26 20:22:00 +01:00
// TODO replace this with setCurrentEpisode
2021-12-20 22:14:58 +01:00
currentEpisodeCr = episodesCrunchy . items . find { episode ->
episode . id == episodeId
} ?: NoneEpisode
println ( " loading playback ${currentEpisodeCr.playback} " )
currentPlaybackCr = Crunchyroll . playback ( currentEpisodeCr . playback )
2020-11-27 11:06:16 +01:00
}
2021-12-26 20:22:00 +01:00
// TODO reimplement for cr
2021-09-05 00:08:03 +02:00
// run async as it should be loaded by the time the episodes a
2021-12-26 20:22:00 +01:00
// viewModelScope.launch {
// // get tmdb season info, if metaDB knows the tv show
// if (media.type == DataTypes.MediaType.TVSHOW && mediaMeta != null) {
// val tvShowMeta = mediaMeta as TVShowMeta
// tmdbTVSeason = TMDBApiController().getTVSeasonDetails(tvShowMeta.tmdbId, tvShowMeta.tmdbSeasonNumber)
// }
// }
2021-09-05 00:08:03 +02:00
2021-09-04 13:33:46 +02:00
currentEpisodeMeta = getEpisodeMetaByAoDMediaId ( currentEpisode . mediaId )
2020-12-31 13:12:37 +01:00
currentLanguage = currentEpisode . getPreferredStream ( preferredLanguage ) . language
2020-12-15 23:15:14 +01:00
}
2020-12-27 20:11:01 +01:00
fun setLanguage ( language : Locale ) {
currentLanguage = language
2021-12-26 20:22:00 +01:00
playCurrentMedia ( player . currentPosition )
2020-11-25 16:04:04 +01:00
2021-12-26 20:22:00 +01:00
// val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
// MediaItem.fromUri(Uri.parse(currentEpisode.getPreferredStream(language).url))
// )
// playMedia(mediaSource, seekTime)
2020-12-20 20:21:27 +01:00
}
2020-12-26 20:09:35 +01:00
// player actions
2020-12-27 20:11:01 +01:00
2020-12-26 20:09:35 +01:00
fun seekToOffset ( offset : Long ) {
player . seekTo ( player . currentPosition + offset )
}
fun togglePausePlay ( ) {
if ( player . isPlaying ) player . pause ( ) else player . play ( )
}
2020-12-27 20:11:01 +01:00
/ * *
* play the next episode , if nextEpisode is not null
* /
2021-12-26 20:22:00 +01:00
fun playNextEpisode ( ) = currentEpisodeCr . nextEpisodeId ?. let { nextEpisodeId ->
setCurrentEpisode ( nextEpisodeId , startPlayback = true )
}
/ * *
* Set currentEpisodeCr to the episode of the given ID
* @param episodeId The ID of the episode you want to set currentEpisodeCr to
* /
fun setCurrentEpisode ( episodeId : String , startPlayback : Boolean = false ) {
currentEpisodeCr = episodesCrunchy . items . find { episode ->
episode . id == episodeId
} ?: NoneEpisode
// TODO don't run blocking
runBlocking {
currentPlaybackCr = Crunchyroll . playback ( currentEpisodeCr . playback )
}
// TODO update metadata and language (it should not be needed to update the language here!)
if ( startPlayback ) {
playCurrentMedia ( )
}
2020-12-27 20:11:01 +01:00
}
/ * *
2021-12-26 20:22:00 +01:00
* Play the current media from currentPlaybackCr .
2020-12-27 20:11:01 +01:00
*
2021-09-05 11:54:55 +02:00
* @param seekPosition The seek position for the episode ( default = 0 ) .
2020-12-27 20:11:01 +01:00
* /
2021-12-26 20:22:00 +01:00
fun playCurrentMedia ( seekPosition : Long = 0 ) {
2021-09-05 11:54:55 +02:00
// update player gui (title, next ep button) after nextEpisodeId has been set
currentEpisodeChangedListener . forEach { it ( ) }
2020-12-27 20:11:01 +01:00
2021-12-26 20:22:00 +01:00
// get preferred stream url TODO implement
val url = currentPlaybackCr . streams . adaptive _hls [ " en-US " ] ?. url ?: " "
println ( " stream url: $url " )
// create the media source object
2020-12-26 20:09:35 +01:00
val mediaSource = HlsMediaSource . Factory ( dataSourceFactory ) . createMediaSource (
2021-12-26 20:22:00 +01:00
MediaItem . fromUri ( Uri . parse ( url ) )
2020-12-26 20:09:35 +01:00
)
2020-12-27 20:11:01 +01:00
2021-12-26 20:22:00 +01:00
// the actual player playback code
player . setMediaSource ( mediaSource )
player . prepare ( )
if ( seekPosition > 0 ) player . seekTo ( seekPosition )
player . playWhenReady = true
2020-12-26 20:09:35 +01:00
2021-12-26 20:22:00 +01:00
// TODO reimplement mark as watched for cr, if needed
2020-11-25 16:04:04 +01:00
}
2020-12-27 20:11:01 +01:00
fun getMediaTitle ( ) : String {
2021-12-26 20:22:00 +01:00
// TODO add tvshow/movie diff
val isTVShow = true
return if ( isTVShow ) {
2020-12-27 20:11:01 +01:00
getApplication < Application > ( ) . getString (
R . string . component _episode _title ,
2021-12-26 20:22:00 +01:00
currentEpisodeCr . episode ,
currentEpisodeCr . title
2020-12-27 20:11:01 +01:00
)
} else {
2021-12-26 20:22:00 +01:00
// TODO movie
currentEpisodeCr . title
2020-12-27 20:11:01 +01:00
}
}
2021-07-17 19:40:16 +02:00
fun getEpisodeMetaByAoDMediaId ( aodMediaId : Int ) : EpisodeMeta ? {
val meta = mediaMeta
return if ( meta is TVShowMeta ) {
meta . episodes . firstOrNull { it . aodMediaId == aodMediaId }
} else {
null
}
}
2021-12-26 20:22:00 +01:00
// TODO reimplement for cr
2021-07-11 12:56:21 +02:00
private suspend fun loadMediaMeta ( aodId : Int ) : Meta ? {
2021-12-26 20:22:00 +01:00
// return if (media.type == DataTypes.MediaType.TVSHOW) {
// MetaDBController().getTVShowMetadata(aodId)
// } else {
// null
// }
return null
2021-07-11 12:56:21 +02:00
}
2020-11-25 16:04:04 +01:00
/ * *
2021-12-26 20:22:00 +01:00
* TODO reimplement for cr
2021-09-05 11:54:55 +02:00
* Based on the current episodes index , get the next episode .
* @return The next episode or null if there is none .
2020-11-25 16:04:04 +01:00
* /
2021-09-05 11:54:55 +02:00
private fun selectNextEpisode ( ) : Int ? {
2021-12-26 20:22:00 +01:00
// return media.playlist.firstOrNull {
// it.index > media.getEpisodeById(currentEpisode.mediaId).index
// }?.mediaId
return null
2020-11-25 16:04:04 +01:00
}
}