Clean up PlayerActivity and PlayerViewModel

* use Local instead of streamURL to save selected language, this allows nextEp/ selected Eps to use previously selected language
* hide episodes button, if media is a movie
This commit is contained in:
Jannik 2020-12-27 20:11:01 +01:00
parent 94da8c6cee
commit 7845770067
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
5 changed files with 75 additions and 91 deletions

View File

@ -11,7 +11,7 @@ android {
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 30 targetSdkVersion 30
versionCode 2100 //00.02.100 versionCode 2100 //00.02.100
versionName "0.2.91" versionName "0.2.92"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime() resValue "string", "build_time", buildTime()

View File

@ -38,7 +38,6 @@ class PlayerActivity : AppCompatActivity() {
private lateinit var gestureDetector: GestureDetectorCompat private lateinit var gestureDetector: GestureDetectorCompat
private lateinit var timerUpdates: TimerTask private lateinit var timerUpdates: TimerTask
private var nextEpManually = false
private var playWhenReady = true private var playWhenReady = true
private var currentWindow = 0 private var currentWindow = 0
private var playbackPosition: Long = 0 private var playbackPosition: Long = 0
@ -62,13 +61,13 @@ class PlayerActivity : AppCompatActivity() {
intent.getIntExtra(getString(R.string.intent_media_id), 0), intent.getIntExtra(getString(R.string.intent_media_id), 0),
intent.getIntExtra(getString(R.string.intent_episode_id), 0) intent.getIntExtra(getString(R.string.intent_episode_id), 0)
) )
model.currentEpisodeChangedListener.add { onMediaChanged() }
gestureDetector = GestureDetectorCompat(this, PlayerGestureListener()) gestureDetector = GestureDetectorCompat(this, PlayerGestureListener())
initGUI()
initActions() initActions()
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
if (Util.SDK_INT > 23) { if (Util.SDK_INT > 23) {
@ -117,17 +116,10 @@ class PlayerActivity : AppCompatActivity() {
initExoPlayer() initExoPlayer()
initVideoView() initVideoView()
initTimeUpdates() initTimeUpdates()
// add listener after initial media is started
model.currentEpisodeChangedListener.add {
nextEpManually = true // make sure on STATE_ENDED doesn't skip another episode
playCurrentMedia(false)
}
} }
private fun initExoPlayer() { private fun initExoPlayer() {
controller = video_view.findViewById(R.id.exo_controller) controller = video_view.findViewById(R.id.exo_controller)
controller.isAnimationEnabled = false // disable controls (time-bar) animation controller.isAnimationEnabled = false // disable controls (time-bar) animation
model.player.playWhenReady = playWhenReady model.player.playWhenReady = playWhenReady
@ -148,16 +140,13 @@ class PlayerActivity : AppCompatActivity() {
} }
if (state == ExoPlayer.STATE_ENDED && model.nextEpisode != null && Preferences.autoplay) { if (state == ExoPlayer.STATE_ENDED && model.nextEpisode != null && Preferences.autoplay) {
// if next episode btn was clicked, skipp playNextEpisode() on STATE_ENDED playNextEpisode()
if (!nextEpManually) {
playNextEpisode()
}
nextEpManually = false
} }
} }
}) })
playCurrentMedia(true) // start initial media // start playing the current episode, after all needed player components have been initialized
model.playEpisode(model.currentEpisode, true, playbackPosition)
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@ -188,6 +177,12 @@ class PlayerActivity : AppCompatActivity() {
button_next_ep_c.setOnClickListener { playNextEpisode() } button_next_ep_c.setOnClickListener { playNextEpisode() }
} }
private fun initGUI() {
if (model.media.type == DataTypes.MediaType.MOVIE) {
button_episodes.visibility = View.GONE
}
}
private fun initTimeUpdates() { private fun initTimeUpdates() {
if (this::timerUpdates.isInitialized) { if (this::timerUpdates.isInitialized) {
timerUpdates.cancel() timerUpdates.cancel()
@ -250,6 +245,17 @@ class PlayerActivity : AppCompatActivity() {
} }
} }
/**
* update title text and next ep button visibility, set ignoreNextStateEnded
*/
private fun onMediaChanged() {
exo_text_title.text = model.getMediaTitle()
if (model.nextEpisode == null) {
button_next_ep_c.visibility = View.GONE
}
}
/** /**
* TODO set position of rewind/fast forward indicators programmatically * TODO set position of rewind/fast forward indicators programmatically
*/ */
@ -290,36 +296,11 @@ class PlayerActivity : AppCompatActivity() {
ffwd_10_indicator.runOnClickAnimation() ffwd_10_indicator.runOnClickAnimation()
} }
private fun playNextEpisode() = model.nextEpisode?.let { private fun playNextEpisode() {
model.nextEpisode() // current = next, next = new or null model.playNextEpisode()
hideButtonNextEp() hideButtonNextEp()
} }
/**
* start playing a episode
* Note: movies are episodes too!
*/
private fun playCurrentMedia(seekToPosition: Boolean) {
// update the gui
exo_text_title.text = if (model.media.type == DataTypes.MediaType.TVSHOW) {
getString(
R.string.component_episode_title,
model.currentEpisode.number,
model.currentEpisode.description
)
} else {
model.currentEpisode.title
}
if (model.nextEpisode == null) {
button_next_ep_c.visibility = View.GONE
}
// update player/media item
val seekPosition = if (seekToPosition) playbackPosition else 0
model.playMedia(model.currentEpisode, true, seekPosition)
}
/** /**
* hide the status and navigation bar * hide the status and navigation bar
*/ */

View File

@ -11,6 +11,7 @@ import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util import com.google.android.exoplayer2.util.Util
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.mosad.teapod.R
import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.parser.AoDParser
import org.mosad.teapod.preferences.Preferences import org.mosad.teapod.preferences.Preferences
import org.mosad.teapod.ui.fragments.MediaFragment import org.mosad.teapod.ui.fragments.MediaFragment
@ -19,7 +20,6 @@ import org.mosad.teapod.util.Episode
import org.mosad.teapod.util.Media import org.mosad.teapod.util.Media
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.properties.Delegates
/** /**
* PlayerViewModel handles all stuff related to media/episodes. * PlayerViewModel handles all stuff related to media/episodes.
@ -35,18 +35,12 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
var media: Media = Media(-1, "", DataTypes.MediaType.OTHER) var media: Media = Media(-1, "", DataTypes.MediaType.OTHER)
internal set internal set
var currentEpisode = Episode()
// TODO rework
var currentEpisode: Episode by Delegates.observable(Episode()) { _, _, _ ->
currentEpisodeChangedListener.forEach { it() }
MediaFragment.instance.updateWatchedState(currentEpisode) // watchedCallback for the new episode
currentStreamUrl = autoSelectStream(currentEpisode)
nextEpisode = selectNextEpisode() // update next ep
}
var currentStreamUrl = "" // TODO don't save selected stream for language, instead save selected language
internal set internal set
var nextEpisode: Episode? = null var nextEpisode: Episode? = null
internal set internal set
var currentLanguage: Locale = Locale.ROOT
internal set
fun loadMedia(mediaId: Int, episodeId: Int) { fun loadMedia(mediaId: Int, episodeId: Int) {
runBlocking { runBlocking {
@ -54,31 +48,23 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
} }
currentEpisode = media.getEpisodeById(episodeId) currentEpisode = media.getEpisodeById(episodeId)
currentStreamUrl = autoSelectStream(currentEpisode) currentLanguage = if (Preferences.preferSecondary) Locale.JAPANESE else Locale.GERMAN
nextEpisode = selectNextEpisode() nextEpisode = selectNextEpisode()
} }
fun changeLanguage(url: String) { fun setLanguage(language: Locale) {
println("new stream is: $url") println("new language is: $language")
currentLanguage = language
val seekTime = player.currentPosition val seekTime = player.currentPosition
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource( val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
MediaItem.fromUri(Uri.parse(url)) MediaItem.fromUri(Uri.parse(currentEpisode.getPreferredStream(language).url))
) )
currentStreamUrl = url
playMedia(mediaSource, true, seekTime) playMedia(mediaSource, true, seekTime)
} }
/**
* update currentEpisode
* updateWatchedState for the next (now current) episode
*/
fun nextEpisode() = nextEpisode?.let { nextEp ->
currentEpisode = nextEp // set current ep to next ep
}
// player actions // player actions
fun seekToOffset(offset: Long) { fun seekToOffset(offset: Long) {
player.seekTo(player.currentPosition + offset) player.seekTo(player.currentPosition + offset)
} }
@ -87,12 +73,30 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
if (player.isPlaying) player.pause() else player.play() if (player.isPlaying) player.pause() else player.play()
} }
fun playMedia(episode: Episode, replace: Boolean = false, seekPosition: Long = 0) { /**
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource( * play the next episode, if nextEpisode is not null
MediaItem.fromUri(Uri.parse(autoSelectStream(episode))) */
) 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)
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
MediaItem.fromUri(Uri.parse( episode.getPreferredStream(currentLanguage).url))
)
playMedia(mediaSource, replace, seekPosition) playMedia(mediaSource, replace, seekPosition)
MediaFragment.instance.updateWatchedState(currentEpisode) // watchedCallback for the new episode
} }
fun playMedia(source: MediaSource, replace: Boolean = false, seekPosition: Long = 0) { fun playMedia(source: MediaSource, replace: Boolean = false, seekPosition: Long = 0) {
@ -104,6 +108,18 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
} }
} }
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
}
}
/** /**
* Based on the current episodeId, get the next episode. If there is no next * Based on the current episodeId, get the next episode. If there is no next
* episode, return null * episode, return null
@ -117,18 +133,4 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
} }
} }
/**
* 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
}
}
} }

View File

@ -32,7 +32,7 @@ class EpisodesListPlayer @JvmOverloads constructor(
adapterRecEpisodes.onImageClick = { _, position -> adapterRecEpisodes.onImageClick = { _, position ->
(this.parent as ViewGroup).removeView(this) (this.parent as ViewGroup).removeView(this)
model.currentEpisode = model.media.episodes[position] model.playEpisode(model.media.episodes[position], replace = true)
} }
binding.recyclerEpisodesPlayer.adapter = adapterRecEpisodes binding.recyclerEpisodesPlayer.adapter = adapterRecEpisodes

View File

@ -14,6 +14,7 @@ import androidx.core.view.children
import org.mosad.teapod.R import org.mosad.teapod.R
import org.mosad.teapod.databinding.PlayerLanguageSettingsBinding import org.mosad.teapod.databinding.PlayerLanguageSettingsBinding
import org.mosad.teapod.player.PlayerViewModel import org.mosad.teapod.player.PlayerViewModel
import java.util.*
class LanguageSettingsPlayer @JvmOverloads constructor( class LanguageSettingsPlayer @JvmOverloads constructor(
context: Context, context: Context,
@ -25,13 +26,13 @@ class LanguageSettingsPlayer @JvmOverloads constructor(
private val binding = PlayerLanguageSettingsBinding.inflate(LayoutInflater.from(context), this, true) private val binding = PlayerLanguageSettingsBinding.inflate(LayoutInflater.from(context), this, true)
var onViewRemovedAction: (() -> Unit)? = null // TODO find a better solution for this var onViewRemovedAction: (() -> Unit)? = null // TODO find a better solution for this
private var currentStreamUrl = model?.currentStreamUrl ?: "" private var currentLanguage = model?.currentLanguage ?: Locale.ROOT
init { init {
model?.let { model?.let {
model.currentEpisode.streams.forEach { stream -> model.currentEpisode.streams.forEach { stream ->
addLanguage(stream.language.displayName, stream.url == currentStreamUrl) { addLanguage(stream.language.displayName, stream.language == currentLanguage) {
currentStreamUrl = stream.url currentLanguage = stream.language
updateSelectedLanguage(it as TextView) updateSelectedLanguage(it as TextView)
} }
} }
@ -40,7 +41,7 @@ class LanguageSettingsPlayer @JvmOverloads constructor(
binding.buttonCloseLanguageSettings.setOnClickListener { close() } binding.buttonCloseLanguageSettings.setOnClickListener { close() }
binding.buttonCancel.setOnClickListener { close() } binding.buttonCancel.setOnClickListener { close() }
binding.buttonSelect.setOnClickListener { binding.buttonSelect.setOnClickListener {
model?.changeLanguage(currentStreamUrl) model?.setLanguage(currentLanguage)
close() close()
} }
} }