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:
parent
94da8c6cee
commit
7845770067
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue