added skip opening for tv shows
* available for tv shows, where metaDB has the needed information
This commit is contained in:
parent
be6e9979a9
commit
876ed97d6d
|
@ -32,10 +32,7 @@ import org.mosad.teapod.R
|
||||||
import org.mosad.teapod.preferences.Preferences
|
import org.mosad.teapod.preferences.Preferences
|
||||||
import org.mosad.teapod.ui.components.EpisodesListPlayer
|
import org.mosad.teapod.ui.components.EpisodesListPlayer
|
||||||
import org.mosad.teapod.ui.components.LanguageSettingsPlayer
|
import org.mosad.teapod.ui.components.LanguageSettingsPlayer
|
||||||
import org.mosad.teapod.util.DataTypes
|
import org.mosad.teapod.util.*
|
||||||
import org.mosad.teapod.util.hideBars
|
|
||||||
import org.mosad.teapod.util.isInPiPMode
|
|
||||||
import org.mosad.teapod.util.navToLauncherTask
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.concurrent.scheduleAtFixedRate
|
import kotlin.concurrent.scheduleAtFixedRate
|
||||||
|
@ -226,7 +223,10 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
// when the player controls get hidden, hide the bars too
|
// when the player controls get hidden, hide the bars too
|
||||||
video_view.setControllerVisibilityListener {
|
video_view.setControllerVisibilityListener {
|
||||||
when (it) {
|
when (it) {
|
||||||
View.GONE -> hideBars()
|
View.GONE -> {
|
||||||
|
hideBars()
|
||||||
|
// TODO also hide the skip op button
|
||||||
|
}
|
||||||
View.VISIBLE -> updateControls()
|
View.VISIBLE -> updateControls()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,6 +244,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
rwd_10.setOnButtonClickListener { rewind() }
|
rwd_10.setOnButtonClickListener { rewind() }
|
||||||
ffwd_10.setOnButtonClickListener { fastForward() }
|
ffwd_10.setOnButtonClickListener { fastForward() }
|
||||||
button_next_ep.setOnClickListener { playNextEpisode() }
|
button_next_ep.setOnClickListener { playNextEpisode() }
|
||||||
|
button_skip_op.setOnClickListener { skipOpening() }
|
||||||
button_language.setOnClickListener { showLanguageSettings() }
|
button_language.setOnClickListener { showLanguageSettings() }
|
||||||
button_episodes.setOnClickListener { showEpisodesList() }
|
button_episodes.setOnClickListener { showEpisodesList() }
|
||||||
button_next_ep_c.setOnClickListener { playNextEpisode() }
|
button_next_ep_c.setOnClickListener { playNextEpisode() }
|
||||||
|
@ -262,16 +263,20 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
|
|
||||||
timerUpdates = Timer().scheduleAtFixedRate(0, 500) {
|
timerUpdates = Timer().scheduleAtFixedRate(0, 500) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
val currentPosition = model.player.currentPosition
|
||||||
val btnNextEpIsVisible = button_next_ep.isVisible
|
val btnNextEpIsVisible = button_next_ep.isVisible
|
||||||
val controlsVisible = controller.isVisible
|
val controlsVisible = controller.isVisible
|
||||||
|
|
||||||
|
// make sure remaining time is > 0
|
||||||
if (model.player.duration > 0) {
|
if (model.player.duration > 0) {
|
||||||
remainingTime = model.player.duration - model.player.currentPosition
|
remainingTime = model.player.duration - currentPosition
|
||||||
remainingTime = if (remainingTime < 0) 0 else remainingTime
|
remainingTime = if (remainingTime < 0) 0 else remainingTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO add metaDB ending_start support
|
||||||
|
// if remaining time < 20 sec, a next ep is set, autoplay is enabled and not in pip:
|
||||||
|
// show next ep button
|
||||||
if (remainingTime in 1..20000) {
|
if (remainingTime in 1..20000) {
|
||||||
// if the next ep button is not visible, make it visible. Don't show in pip mode
|
|
||||||
if (!btnNextEpIsVisible && model.nextEpisode != null && Preferences.autoplay && !isInPiPMode()) {
|
if (!btnNextEpIsVisible && model.nextEpisode != null && Preferences.autoplay && !isInPiPMode()) {
|
||||||
showButtonNextEp()
|
showButtonNextEp()
|
||||||
}
|
}
|
||||||
|
@ -279,6 +284,19 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
hideButtonNextEp()
|
hideButtonNextEp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if meta data is present and opening_start & opening_duration are valid, show skip opening
|
||||||
|
model.currentEpisodeMeta?.let {
|
||||||
|
if (it.openingDuration > 0 &&
|
||||||
|
currentPosition in it.openingStart..(it.openingStart + 10000) &&
|
||||||
|
!button_skip_op.isVisible
|
||||||
|
) {
|
||||||
|
showButtonSkipOp()
|
||||||
|
} else if (button_skip_op.isVisible && currentPosition !in it.openingStart..(it.openingStart + 10000)) {
|
||||||
|
// the button should only be visible, if currentEpisodeMeta != null
|
||||||
|
hideButtonSkipOp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if controls are visible, update them
|
// if controls are visible, update them
|
||||||
if (controlsVisible) {
|
if (controlsVisible) {
|
||||||
updateControls()
|
updateControls()
|
||||||
|
@ -376,12 +394,21 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
hideButtonNextEp()
|
hideButtonNextEp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun skipOpening() {
|
||||||
|
// calculate the seek time
|
||||||
|
model.currentEpisodeMeta?.let {
|
||||||
|
val seekTime = (it.openingStart + it.openingDuration) - model.player.currentPosition
|
||||||
|
model.seekToOffset(seekTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show the next episode button
|
* show the next episode button
|
||||||
* TODO improve the show animation
|
* TODO improve the show animation
|
||||||
*/
|
*/
|
||||||
private fun showButtonNextEp() {
|
private fun showButtonNextEp() {
|
||||||
button_next_ep.visibility = View.VISIBLE
|
button_next_ep.isVisible = true
|
||||||
button_next_ep.alpha = 0.0f
|
button_next_ep.alpha = 0.0f
|
||||||
|
|
||||||
button_next_ep.animate()
|
button_next_ep.animate()
|
||||||
|
@ -399,7 +426,28 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
.setListener(object : AnimatorListenerAdapter() {
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
super.onAnimationEnd(animation)
|
super.onAnimationEnd(animation)
|
||||||
button_next_ep.visibility = View.GONE
|
button_next_ep.isVisible = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showButtonSkipOp() {
|
||||||
|
button_skip_op.isVisible = true
|
||||||
|
button_skip_op.alpha = 0.0f
|
||||||
|
|
||||||
|
button_skip_op.animate()
|
||||||
|
.alpha(1.0f)
|
||||||
|
.setListener(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideButtonSkipOp() {
|
||||||
|
button_skip_op.animate()
|
||||||
|
.alpha(0.0f)
|
||||||
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
super.onAnimationEnd(animation)
|
||||||
|
button_skip_op.isVisible = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,9 @@ import kotlin.collections.ArrayList
|
||||||
* PlayerViewModel handles all stuff related to media/episodes.
|
* PlayerViewModel handles all stuff related to media/episodes.
|
||||||
* When currentEpisode is changed the player will start playing it (not initial media),
|
* When currentEpisode is changed the player will start playing it (not initial media),
|
||||||
* the next episode will be update and the callback is handled.
|
* the next episode will be update and the callback is handled.
|
||||||
|
*
|
||||||
|
* TODO rework don't use episodes for everything, use media instead
|
||||||
|
* this is a major rework of the AoDParser/Player/Media architecture
|
||||||
*/
|
*/
|
||||||
class PlayerViewModel(application: Application) : AndroidViewModel(application) {
|
class PlayerViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
|
@ -45,6 +48,8 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
internal set
|
internal set
|
||||||
var mediaMeta: Meta? = null
|
var mediaMeta: Meta? = null
|
||||||
internal set
|
internal set
|
||||||
|
var currentEpisodeMeta: EpisodeMeta? = null
|
||||||
|
internal set
|
||||||
var currentLanguage: Locale = Locale.ROOT
|
var currentLanguage: Locale = Locale.ROOT
|
||||||
internal set
|
internal set
|
||||||
|
|
||||||
|
@ -75,11 +80,12 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
fun loadMedia(mediaId: Int, episodeId: Int) {
|
fun loadMedia(mediaId: Int, episodeId: Int) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
media = AoDParser.getMediaById(mediaId)
|
media = AoDParser.getMediaById(mediaId)
|
||||||
mediaMeta = loadMediaMeta(media.id)
|
mediaMeta = loadMediaMeta(media.id) // can be done blocking, since it should be cached
|
||||||
}
|
}
|
||||||
|
|
||||||
currentEpisode = media.getEpisodeById(episodeId)
|
currentEpisode = media.getEpisodeById(episodeId)
|
||||||
nextEpisode = selectNextEpisode()
|
nextEpisode = selectNextEpisode()
|
||||||
|
currentEpisodeMeta = getEpisodeMetaByAoDMediaId(currentEpisode.id)
|
||||||
currentLanguage = currentEpisode.getPreferredStream(preferredLanguage).language
|
currentLanguage = currentEpisode.getPreferredStream(preferredLanguage).language
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +127,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
currentLanguage = preferredStream.language // update current language, since it may have changed
|
currentLanguage = preferredStream.language // update current language, since it may have changed
|
||||||
currentEpisode = episode
|
currentEpisode = episode
|
||||||
nextEpisode = selectNextEpisode()
|
nextEpisode = selectNextEpisode()
|
||||||
|
currentEpisodeMeta = getEpisodeMetaByAoDMediaId(episode.id)
|
||||||
currentEpisodeChangedListener.forEach { it() } // update player gui (title)
|
currentEpisodeChangedListener.forEach { it() } // update player gui (title)
|
||||||
|
|
||||||
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
|
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
|
||||||
|
@ -160,6 +167,15 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getEpisodeMetaByAoDMediaId(aodMediaId: Int): EpisodeMeta? {
|
||||||
|
val meta = mediaMeta
|
||||||
|
return if (meta is TVShowMeta) {
|
||||||
|
meta.episodes.firstOrNull { it.aodMediaId == aodMediaId }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun loadMediaMeta(aodId: Int): Meta? {
|
private suspend fun loadMediaMeta(aodId: Int): Meta? {
|
||||||
return if (media.type == DataTypes.MediaType.TVSHOW) {
|
return if (media.type == DataTypes.MediaType.TVSHOW) {
|
||||||
MetaDBController().getTVShowMetadata(aodId)
|
MetaDBController().getTVShowMetadata(aodId)
|
||||||
|
|
|
@ -42,8 +42,6 @@ class MetaDBController {
|
||||||
val url = URL("$repoUrl/list.json")
|
val url = URL("$repoUrl/list.json")
|
||||||
val json = url.readText()
|
val json = url.readText()
|
||||||
|
|
||||||
Thread.sleep(5000)
|
|
||||||
|
|
||||||
mediaList = Gson().fromJson(json, MediaList::class.java)
|
mediaList = Gson().fromJson(json, MediaList::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,11 +146,11 @@ data class EpisodeMeta(
|
||||||
@SerializedName("tmdb_number")
|
@SerializedName("tmdb_number")
|
||||||
val tmdbNumber: Int,
|
val tmdbNumber: Int,
|
||||||
@SerializedName("opening_start")
|
@SerializedName("opening_start")
|
||||||
val openingStart: Int,
|
val openingStart: Long,
|
||||||
@SerializedName("opening_duration")
|
@SerializedName("opening_duration")
|
||||||
val openingDuration: Int,
|
val openingDuration: Long,
|
||||||
@SerializedName("ending_start")
|
@SerializedName("ending_start")
|
||||||
val endingStart: Int,
|
val endingStart: Long,
|
||||||
@SerializedName("ending_duration")
|
@SerializedName("ending_duration")
|
||||||
val endingDuration: Int
|
val endingDuration: Long
|
||||||
)
|
)
|
||||||
|
|
|
@ -89,4 +89,20 @@
|
||||||
app:backgroundTint="@color/exo_white"
|
app:backgroundTint="@color/exo_white"
|
||||||
app:iconGravity="textStart" />
|
app:iconGravity="textStart" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_skip_op"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_marginBottom="70dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/skip_opening"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@android:color/primary_text_light"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:backgroundTint="@color/exo_white"
|
||||||
|
app:iconGravity="textStart" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -69,6 +69,7 @@
|
||||||
<string name="play_pause">Abspielen/Pause</string>
|
<string name="play_pause">Abspielen/Pause</string>
|
||||||
<string name="forward_10">10 Sekunden vorwärts</string>
|
<string name="forward_10">10 Sekunden vorwärts</string>
|
||||||
<string name="next_episode">Nächste Folge</string>
|
<string name="next_episode">Nächste Folge</string>
|
||||||
|
<string name="skip_opening">Intro überspringen</string>
|
||||||
<string name="language">Sprache</string>
|
<string name="language">Sprache</string>
|
||||||
<string name="episodes">Folgen</string>
|
<string name="episodes">Folgen</string>
|
||||||
<string name="episode">Folge</string>
|
<string name="episode">Folge</string>
|
||||||
|
|
|
@ -88,6 +88,7 @@
|
||||||
<string name="rwd_10_s" translatable="false">- 10 s</string>
|
<string name="rwd_10_s" translatable="false">- 10 s</string>
|
||||||
<string name="fwd_10_s" translatable="false">+ 10 s</string>
|
<string name="fwd_10_s" translatable="false">+ 10 s</string>
|
||||||
<string name="next_episode">Next Episode</string>
|
<string name="next_episode">Next Episode</string>
|
||||||
|
<string name="skip_opening">Skip Opening</string>
|
||||||
<string name="time_min_sec" translatable="false">%1$02d:%2$02d</string>
|
<string name="time_min_sec" translatable="false">%1$02d:%2$02d</string>
|
||||||
<string name="time_hour_min_sec" translatable="false">%1$d:%2$02d:%3$02d</string>
|
<string name="time_hour_min_sec" translatable="false">%1$d:%2$02d:%3$02d</string>
|
||||||
<string name="language">Language</string>
|
<string name="language">Language</string>
|
||||||
|
|
Loading…
Reference in New Issue