player language settings [Part 2]

* move player object to PlayerViewModel
* minor code clean up
This commit is contained in:
2020-12-26 20:09:35 +01:00
parent 8a43567737
commit 94da8c6cee
7 changed files with 151 additions and 90 deletions

View File

@ -3,7 +3,6 @@ package org.mosad.teapod.player
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
@ -13,13 +12,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GestureDetectorCompat
import androidx.core.view.isVisible
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.ui.StyledPlayerControlView
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import kotlinx.android.synthetic.main.activity_player.*
import kotlinx.android.synthetic.main.player_controls.*
@ -40,8 +34,6 @@ class PlayerActivity : AppCompatActivity() {
private val model: PlayerViewModel by viewModels()
private lateinit var player: SimpleExoPlayer
private lateinit var dataSourceFactory: DataSource.Factory
private lateinit var controller: StyledPlayerControlView
private lateinit var gestureDetector: GestureDetectorCompat
private lateinit var timerUpdates: TimerTask
@ -52,8 +44,8 @@ class PlayerActivity : AppCompatActivity() {
private var playbackPosition: Long = 0
private var remainingTime: Long = 0
private val rwdTime = 10000
private val fwdTime = 10000
private val rwdTime: Long = 10000.unaryMinus()
private val fwdTime: Long = 10000
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -117,8 +109,8 @@ class PlayerActivity : AppCompatActivity() {
}
private fun initPlayer() {
if (model.mediaId <= 0) {
Log.e(javaClass.name, "No media id was set.")
if (model.media.id < 0) {
Log.e(javaClass.name, "No media was set.")
this.finish()
}
@ -134,14 +126,12 @@ class PlayerActivity : AppCompatActivity() {
}
private fun initExoPlayer() {
player = SimpleExoPlayer.Builder(this).build()
dataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, "Teapod"))
controller = video_view.findViewById(R.id.exo_controller)
controller.isAnimationEnabled = false // disable controls (time-bar) animation
player.playWhenReady = playWhenReady
player.addListener(object : Player.EventListener {
model.player.playWhenReady = playWhenReady
model.player.addListener(object : Player.EventListener {
override fun onPlaybackStateChanged(state: Int) {
super.onPlaybackStateChanged(state)
@ -172,7 +162,7 @@ class PlayerActivity : AppCompatActivity() {
@SuppressLint("ClickableViewAccessibility")
private fun initVideoView() {
video_view.player = player
video_view.player = model.player
// when the player controls get hidden, hide the bars too
video_view.setControllerVisibilityListener {
@ -208,10 +198,10 @@ class PlayerActivity : AppCompatActivity() {
var btnNextEpIsVisible: Boolean
var controlsVisible: Boolean
withContext(Dispatchers.Main) {
remainingTime = player.duration - player.currentPosition
remainingTime = if (remainingTime < 0) 0 else remainingTime
remainingTime = model.player.duration - model.player.currentPosition
remainingTime = if (remainingTime < 0) 0 else remainingTime
withContext(Dispatchers.Main) {
btnNextEpIsVisible = button_next_ep.isVisible
controlsVisible = controller.isVisible
}
@ -234,10 +224,10 @@ class PlayerActivity : AppCompatActivity() {
}
private fun releasePlayer(){
playbackPosition = player.currentPosition
currentWindow = player.currentWindowIndex
playWhenReady = player.playWhenReady
player.release()
playbackPosition = model.player.currentPosition
currentWindow = model.player.currentWindowIndex
playWhenReady = model.player.playWhenReady
model.player.release()
timerUpdates.cancel()
Log.d(javaClass.name, "Released player")
@ -265,7 +255,7 @@ class PlayerActivity : AppCompatActivity() {
*/
private fun rewind() {
player.seekTo(player.currentPosition - rwdTime)
model.seekToOffset(rwdTime)
// hide/show needed components
exo_double_tap_indicator.visibility = View.VISIBLE
@ -283,7 +273,7 @@ class PlayerActivity : AppCompatActivity() {
}
private fun fastForward() {
player.seekTo(player.currentPosition + fwdTime)
model.seekToOffset(fwdTime)
// hide/show needed components
exo_double_tap_indicator.visibility = View.VISIBLE
@ -300,10 +290,6 @@ class PlayerActivity : AppCompatActivity() {
ffwd_10_indicator.runOnClickAnimation()
}
private fun togglePausePlay() {
if (player.isPlaying) player.pause() else player.play()
}
private fun playNextEpisode() = model.nextEpisode?.let {
model.nextEpisode() // current = next, next = new or null
hideButtonNextEp()
@ -330,15 +316,8 @@ class PlayerActivity : AppCompatActivity() {
}
// update player/media item
player.playWhenReady = true
player.clearMediaItems() //remove previous item
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
MediaItem.fromUri(Uri.parse(model.autoSelectStream(model.currentEpisode)))
)
if (seekToPosition) player.seekTo(playbackPosition)
player.setMediaSource(mediaSource)
player.prepare()
val seekPosition = if (seekToPosition) playbackPosition else 0
model.playMedia(model.currentEpisode, true, seekPosition)
}
/**
@ -393,23 +372,23 @@ class PlayerActivity : AppCompatActivity() {
private fun showEpisodesList() {
val episodesList = EpisodesListPlayer(this, model = model).apply {
onViewRemovedAction = { player.play() }
onViewRemovedAction = { model.player.play() }
}
player_layout.addView(episodesList)
// hide player controls and pause playback
player.pause()
model.player.pause()
controller.hide()
}
private fun showLanguageSettings() {
val languageSettings = LanguageSettingsPlayer(this, model = model).apply {
onViewRemovedAction = { player.play() }
onViewRemovedAction = { model.player.play() }
}
player_layout.addView(languageSettings)
// hide player controls and pause playback
player.pause()
model.player.pause()
controller.hideImmediately()
}
@ -447,7 +426,7 @@ class PlayerActivity : AppCompatActivity() {
* on long press toggle pause/play
*/
override fun onLongPress(e: MotionEvent?) {
togglePausePlay()
model.togglePausePlay()
}
}

View File

@ -1,7 +1,15 @@
package org.mosad.teapod.player
import android.util.Log
import androidx.lifecycle.ViewModel
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
import kotlinx.coroutines.runBlocking
import org.mosad.teapod.parser.AoDParser
import org.mosad.teapod.preferences.Preferences
@ -18,63 +26,82 @@ import kotlin.properties.Delegates
* When currentEpisode is changed the player will start playing it (not initial media),
* the next episode will be update and the callback is handled.
*/
class PlayerViewModel : ViewModel() {
class PlayerViewModel(application: Application) : AndroidViewModel(application) {
val player = SimpleExoPlayer.Builder(application).build()
val dataSourceFactory = DefaultDataSourceFactory(application, Util.getUserAgent(application, "Teapod"))
val currentEpisodeChangedListener = ArrayList<() -> Unit>()
var mediaId = 0
internal set
var episodeId = 0
var media: Media = Media(-1, "", DataTypes.MediaType.OTHER)
internal set
var media: Media = Media(0, "", DataTypes.MediaType.OTHER)
internal set
// 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
var nextEpisode: Episode? = null
internal set
fun loadMedia(iMediaId: Int, iEpisodeId: Int) {
mediaId = iMediaId
episodeId = iEpisodeId
fun loadMedia(mediaId: Int, episodeId: Int) {
runBlocking {
media = AoDParser.getMediaById(mediaId)
}
currentEpisode = media.episodes.first { it.id == episodeId }
currentEpisode = media.getEpisodeById(episodeId)
currentStreamUrl = autoSelectStream(currentEpisode)
nextEpisode = selectNextEpisode()
}
currentEpisode
fun changeLanguage(url: String) {
println("new stream is: $url")
val seekTime = player.currentPosition
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
MediaItem.fromUri(Uri.parse(url))
)
currentStreamUrl = url
playMedia(mediaSource, true, seekTime)
}
/**
* If preferSecondary or priStreamUrl is empty and secondary is present (secStreamOmU),
* use the secondary stream. Else, if the primary stream is set use the primary stream.
* If no stream is present, return empty string.
*/
fun autoSelectStream(episode: Episode): String {
return if (Preferences.preferSecondary) {
episode.getPreferredStream(Locale.JAPANESE).url
} else {
episode.getPreferredStream(Locale.GERMAN).url
}
}
fun changeLanguage(id: Int) {
println("new Language is ABC with id $id")
}
/**
* update currentEpisode, episodeId, nextEpisode to new episode
* update currentEpisode
* updateWatchedState for the next (now current) episode
*/
fun nextEpisode() = nextEpisode?.let { nextEp ->
currentEpisode = nextEp // set current ep to next ep
episodeId = nextEp.id
}
// 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
}
}
/**
@ -90,4 +117,18 @@ class PlayerViewModel : ViewModel() {
}
}
/**
* 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
}
}
}