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
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-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-12-15 23:15:14 +01:00
import kotlin.properties.Delegates
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-26 20:09:35 +01:00
// TODO rework
2020-12-15 23:15:14 +01:00
var currentEpisode : Episode by Delegates . observable ( Episode ( ) ) { _ , _ , _ ->
currentEpisodeChangedListener . forEach { it ( ) }
MediaFragment . instance . updateWatchedState ( currentEpisode ) // watchedCallback for the new episode
2020-12-26 20:09:35 +01:00
currentStreamUrl = autoSelectStream ( currentEpisode )
2020-12-15 23:15:14 +01:00
nextEpisode = selectNextEpisode ( ) // update next ep
}
2020-12-26 20:09:35 +01:00
var currentStreamUrl = " " // TODO don't save selected stream for language, instead save selected language
internal set
2020-11-25 16:04:04 +01:00
var nextEpisode : Episode ? = null
internal set
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 )
currentStreamUrl = autoSelectStream ( currentEpisode )
2020-11-25 16:04:04 +01:00
nextEpisode = selectNextEpisode ( )
2020-12-15 23:15:14 +01:00
}
2020-12-26 20:09:35 +01:00
fun changeLanguage ( url : String ) {
println ( " new stream is: $url " )
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 (
MediaItem . fromUri ( Uri . parse ( url ) )
)
currentStreamUrl = url
playMedia ( mediaSource , true , seekTime )
2020-12-20 20:21:27 +01:00
}
2020-11-25 16:04:04 +01:00
/ * *
2020-12-26 20:09:35 +01:00
* update currentEpisode
2020-11-25 16:04:04 +01:00
* updateWatchedState for the next ( now current ) episode
* /
fun nextEpisode ( ) = nextEpisode ?. let { nextEp ->
currentEpisode = nextEp // set current ep to next ep
2020-12-26 20:09:35 +01:00
}
// player actions
fun seekToOffset ( offset : Long ) {
player . seekTo ( player . currentPosition + offset )
}
fun togglePausePlay ( ) {
if ( player . isPlaying ) player . pause ( ) else player . play ( )
}
fun playMedia ( episode : Episode , replace : Boolean = false , seekPosition : Long = 0 ) {
val mediaSource = HlsMediaSource . Factory ( dataSourceFactory ) . createMediaSource (
MediaItem . fromUri ( Uri . parse ( autoSelectStream ( episode ) ) )
)
playMedia ( mediaSource , replace , seekPosition )
}
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
}
/ * *
* 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
}
}
2020-12-26 20:09:35 +01:00
/ * *
* If preferSecondary use the japanese stream , if present .
* If the preferred stream is not present the default ( first )
* stream will be used
* /
private fun autoSelectStream ( episode : Episode ) : String {
return if ( Preferences . preferSecondary ) {
episode . getPreferredStream ( Locale . JAPANESE ) . url
} else {
episode . getPreferredStream ( Locale . GERMAN ) . url
}
}
2020-11-25 16:04:04 +01:00
}