2020-11-25 16:04:04 +01:00
package org.mosad.teapod.player
2020-12-26 20:09:35 +01:00
import android.app.Application
import android.net.Uri
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.C
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.source.MediaSource
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
2020-11-25 16:04:04 +01:00
import org.mosad.teapod.parser.AoDParser
2020-12-15 23:15:14 +01:00
import org.mosad.teapod.preferences.Preferences
2020-11-25 16:04:04 +01:00
import org.mosad.teapod.ui.fragments.MediaFragment
import org.mosad.teapod.util.DataTypes
import org.mosad.teapod.util.Episode
import org.mosad.teapod.util.Media
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 ( )
val dataSourceFactory = DefaultDataSourceFactory ( application , Util . getUserAgent ( application , " Teapod " ) )
2020-11-25 16:04:04 +01:00
2020-12-15 23:15:14 +01:00
val currentEpisodeChangedListener = ArrayList < ( ) -> Unit > ( )
2020-12-26 20:09:35 +01:00
var media : Media = Media ( - 1 , " " , DataTypes . MediaType . OTHER )
2020-11-25 16:04:04 +01:00
internal set
2020-12-27 20:11:01 +01:00
var currentEpisode = Episode ( )
2020-12-26 20:09:35 +01:00
internal set
2020-11-25 16:04:04 +01:00
var nextEpisode : Episode ? = 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
2020-12-27 20:31:18 +01:00
override fun onCleared ( ) {
super . onCleared ( )
player . release ( )
Log . d ( javaClass . name , " Released player " )
}
2020-12-26 20:09:35 +01:00
fun loadMedia ( mediaId : Int , episodeId : Int ) {
2020-11-27 11:06:16 +01:00
runBlocking {
media = AoDParser . getMediaById ( mediaId )
}
2020-12-26 20:09:35 +01:00
currentEpisode = media . getEpisodeById ( episodeId )
2020-12-27 20:11:01 +01:00
currentLanguage = if ( Preferences . preferSecondary ) Locale . JAPANESE else Locale . GERMAN
2020-11-25 16:04:04 +01:00
nextEpisode = selectNextEpisode ( )
2020-12-15 23:15:14 +01:00
}
2020-12-27 20:11:01 +01:00
fun setLanguage ( language : Locale ) {
currentLanguage = language
2020-11-25 16:04:04 +01:00
2020-12-26 20:09:35 +01:00
val seekTime = player . currentPosition
val mediaSource = HlsMediaSource . Factory ( dataSourceFactory ) . createMediaSource (
2020-12-27 20:11:01 +01:00
MediaItem . fromUri ( Uri . parse ( currentEpisode . getPreferredStream ( language ) . url ) )
2020-12-26 20:09:35 +01:00
)
playMedia ( mediaSource , true , 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
* /
fun playNextEpisode ( ) = nextEpisode ?. let { it ->
playEpisode ( it , replace = true )
}
/ * *
* set currentEpisode to the param episode and start playing it
* update nextEpisode to reflect the change
*
* updateWatchedState for the next ( now current ) episode
* /
fun playEpisode ( episode : Episode , replace : Boolean = false , seekPosition : Long = 0 ) {
currentEpisode = episode
nextEpisode = selectNextEpisode ( )
currentEpisodeChangedListener . forEach { it ( ) } // update player gui (title)
2020-12-26 20:09:35 +01:00
val mediaSource = HlsMediaSource . Factory ( dataSourceFactory ) . createMediaSource (
2020-12-27 20:11:01 +01:00
MediaItem . fromUri ( Uri . parse ( episode . getPreferredStream ( currentLanguage ) . url ) )
2020-12-26 20:09:35 +01:00
)
playMedia ( mediaSource , replace , seekPosition )
2020-12-27 20:11:01 +01:00
MediaFragment . instance . updateWatchedState ( currentEpisode ) // watchedCallback for the new episode
2020-12-26 20:09:35 +01:00
}
fun playMedia ( source : MediaSource , replace : Boolean = false , seekPosition : Long = 0 ) {
if ( replace || player . contentDuration == C . TIME _UNSET ) {
player . setMediaSource ( source )
player . prepare ( )
if ( seekPosition > 0 ) player . seekTo ( seekPosition )
player . playWhenReady = true
}
2020-11-25 16:04:04 +01:00
}
2020-12-27 20:11:01 +01:00
fun getMediaTitle ( ) : String {
return if ( media . type == DataTypes . MediaType . TVSHOW ) {
getApplication < Application > ( ) . getString (
R . string . component _episode _title ,
currentEpisode . number ,
currentEpisode . description
)
} else {
currentEpisode . title
}
}
2020-11-25 16:04:04 +01:00
/ * *
* Based on the current episodeId , get the next episode . If there is no next
* episode , return null
* /
private fun selectNextEpisode ( ) : Episode ? {
val nextEpIndex = media . episodes . indexOfFirst { it . id == currentEpisode . id } + 1
2020-12-15 23:15:14 +01:00
return if ( nextEpIndex < media . episodes . size ) {
2020-11-25 16:04:04 +01:00
media . episodes [ nextEpIndex ]
} else {
null
}
}
}