Player: add auto play next episode
This commit is contained in:
parent
353ae6937a
commit
23713fc1e6
|
@ -8,6 +8,7 @@ import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.GestureDetectorCompat
|
import androidx.core.view.GestureDetectorCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.Player
|
import com.google.android.exoplayer2.Player
|
||||||
|
@ -19,6 +20,7 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||||
import com.google.android.exoplayer2.util.Util
|
import com.google.android.exoplayer2.util.Util
|
||||||
import kotlinx.android.synthetic.main.activity_player.*
|
import kotlinx.android.synthetic.main.activity_player.*
|
||||||
import kotlinx.android.synthetic.main.player_controls.*
|
import kotlinx.android.synthetic.main.player_controls.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
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.util.DataTypes.MediaType
|
import org.mosad.teapod.util.DataTypes.MediaType
|
||||||
|
@ -33,8 +35,6 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
private lateinit var controller: StyledPlayerControlView
|
private lateinit var controller: StyledPlayerControlView
|
||||||
private lateinit var gestureDetector: GestureDetectorCompat
|
private lateinit var gestureDetector: GestureDetectorCompat
|
||||||
|
|
||||||
private var streamUrl = ""
|
|
||||||
|
|
||||||
private var mediaId = 0
|
private var mediaId = 0
|
||||||
private var episodeId = 0
|
private var episodeId = 0
|
||||||
|
|
||||||
|
@ -119,19 +119,13 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
initExoPlayer()
|
initExoPlayer()
|
||||||
initVideoView()
|
initVideoView()
|
||||||
initController()
|
initController()
|
||||||
|
initTimeUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initMedia() {
|
private fun initMedia() {
|
||||||
media = AoDParser.getMediaById(mediaId)
|
media = AoDParser.getMediaById(mediaId)
|
||||||
currentEpisode = media.episodes.first { it.id == episodeId }
|
currentEpisode = media.episodes.first { it.id == episodeId }
|
||||||
streamUrlFromEp(currentEpisode) // get current stream
|
nextEpisode = selectNextEpisode()
|
||||||
|
|
||||||
// get next episode if present
|
|
||||||
val nextEpIndex = media.episodes.indexOfFirst { it.id == episodeId } + 1
|
|
||||||
if (nextEpIndex < (media.episodes.size - 1)) {
|
|
||||||
println("has next episode")
|
|
||||||
nextEpisode = media.episodes[nextEpIndex]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initExoPlayer() {
|
private fun initExoPlayer() {
|
||||||
|
@ -140,7 +134,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
controller = video_view.findViewById(R.id.exo_controller)
|
controller = video_view.findViewById(R.id.exo_controller)
|
||||||
|
|
||||||
val mediaSource = HlsMediaSource.Factory(dataSourceFactory)
|
val mediaSource = HlsMediaSource.Factory(dataSourceFactory)
|
||||||
.createMediaSource(MediaItem.fromUri(Uri.parse(streamUrl)))
|
.createMediaSource(MediaItem.fromUri(Uri.parse(selectStream(currentEpisode))))
|
||||||
|
|
||||||
player.playWhenReady = playWhenReady
|
player.playWhenReady = playWhenReady
|
||||||
player.setMediaSource(mediaSource)
|
player.setMediaSource(mediaSource)
|
||||||
|
@ -162,6 +156,11 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
View.VISIBLE -> View.INVISIBLE
|
View.VISIBLE -> View.INVISIBLE
|
||||||
else -> View.VISIBLE
|
else -> View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state == ExoPlayer.STATE_ENDED && nextEpisode != null) {
|
||||||
|
playNextEpisode()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -198,6 +197,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.time_hour_min_sec, hours, minutes, seconds)
|
getString(R.string.time_hour_min_sec, hours, minutes, seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exo_text_title.text = currentEpisode.title // set media title
|
exo_text_title.text = currentEpisode.title // set media title
|
||||||
|
@ -207,6 +207,26 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
exo_close_player.setOnClickListener { this.finish() }
|
exo_close_player.setOnClickListener { this.finish() }
|
||||||
exo_rew_10.setOnClickListener { rewind() }
|
exo_rew_10.setOnClickListener { rewind() }
|
||||||
exo_ffwd_10.setOnClickListener { forward() }
|
exo_ffwd_10.setOnClickListener { forward() }
|
||||||
|
button_next_ep.setOnClickListener { playNextEpisode() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initTimeUpdates() = GlobalScope.launch {
|
||||||
|
while (true) {
|
||||||
|
val remainingTime = withContext(Dispatchers.Main) {
|
||||||
|
player.duration - player.currentPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingTime in 0..20000) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
// if the next ep button is not visible, make it visible
|
||||||
|
if (!button_next_ep.isVisible) {
|
||||||
|
button_next_ep.visibility = View.VISIBLE // TODO animation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun releasePlayer(){
|
private fun releasePlayer(){
|
||||||
|
@ -226,11 +246,23 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
player.seekTo(player.currentPosition + fwdTime)
|
player.seekTo(player.currentPosition + fwdTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
private fun playNextEpisode() {
|
private fun playNextEpisode() {
|
||||||
nextEpisode?.let { streamUrlFromEp(it) }
|
nextEpisode?.let { nextEp ->
|
||||||
// TODO play
|
currentEpisode = nextEp // set current ep to next ep
|
||||||
// TODO set next episode if present
|
episodeId = nextEp.id
|
||||||
|
|
||||||
|
// update the gui
|
||||||
|
exo_text_title.text = nextEp.title
|
||||||
|
button_next_ep.visibility = View.GONE // TODO animation
|
||||||
|
|
||||||
|
player.clearMediaItems() //remove previous item
|
||||||
|
val mediaSource = HlsMediaSource.Factory(dataSourceFactory)
|
||||||
|
.createMediaSource(MediaItem.fromUri(Uri.parse(selectStream(nextEp))))
|
||||||
|
player.setMediaSource(mediaSource)
|
||||||
|
player.prepare()
|
||||||
|
|
||||||
|
nextEpisode = selectNextEpisode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -238,18 +270,30 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
* use the secondary stream. Else, if the primary stream is set use the primary stream.
|
* use the secondary stream. Else, if the primary stream is set use the primary stream.
|
||||||
* If no stream is present, close the activity.
|
* If no stream is present, close the activity.
|
||||||
*/
|
*/
|
||||||
private fun streamUrlFromEp(episode: Episode) {
|
private fun selectStream(episode: Episode): String {
|
||||||
streamUrl = if ((Preferences.preferSecondary || episode.priStreamUrl.isEmpty()) && episode.secStreamOmU) {
|
return if ((Preferences.preferSecondary || episode.priStreamUrl.isEmpty()) && episode.secStreamOmU) {
|
||||||
episode.secStreamUrl
|
episode.secStreamUrl
|
||||||
} else if (episode.priStreamUrl.isNotEmpty()) {
|
} else if (episode.priStreamUrl.isNotEmpty()) {
|
||||||
episode.priStreamUrl
|
episode.priStreamUrl
|
||||||
} else {
|
} else {
|
||||||
Log.e(javaClass.name, "No stream url set.")
|
Log.e(javaClass.name, "No stream url set.")
|
||||||
this.finish()
|
this.finish()
|
||||||
return
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
return if (nextEpIndex < (media.episodes.size)) {
|
||||||
|
media.episodes[nextEpIndex]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hide the status and navigation bar
|
* hide the status and navigation bar
|
||||||
|
|
|
@ -73,6 +73,7 @@ object AoDParser {
|
||||||
|
|
||||||
val resLogin = Jsoup.connect(baseUrl + loginPath)
|
val resLogin = Jsoup.connect(baseUrl + loginPath)
|
||||||
.method(Connection.Method.POST)
|
.method(Connection.Method.POST)
|
||||||
|
.timeout(60000) // login can take some time
|
||||||
.data(data)
|
.data(data)
|
||||||
.postDataCharset("UTF-8")
|
.postDataCharset("UTF-8")
|
||||||
.cookies(authCookies)
|
.cookies(authCookies)
|
||||||
|
|
|
@ -17,7 +17,6 @@ import kotlinx.android.synthetic.main.fragment_media.*
|
||||||
import org.mosad.teapod.MainActivity
|
import org.mosad.teapod.MainActivity
|
||||||
import org.mosad.teapod.R
|
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.util.DataTypes.MediaType
|
import org.mosad.teapod.util.DataTypes.MediaType
|
||||||
import org.mosad.teapod.util.Episode
|
import org.mosad.teapod.util.Episode
|
||||||
import org.mosad.teapod.util.Media
|
import org.mosad.teapod.util.Media
|
||||||
|
|
|
@ -24,6 +24,23 @@
|
||||||
android:layout_width="70dp"
|
android:layout_width="70dp"
|
||||||
android:layout_height="70dp"
|
android:layout_height="70dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
app:indicatorColor="@color/exo_white"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_next_ep"
|
||||||
|
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/next_episode"
|
||||||
|
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>
|
|
@ -35,6 +35,8 @@
|
||||||
<string name="rewind_10">10 Sekunden zurück</string>
|
<string name="rewind_10">10 Sekunden zurück</string>
|
||||||
<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 Episode</string>
|
||||||
|
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="save">speichern</string>
|
<string name="save">speichern</string>
|
||||||
<string name="cancel">@android:string/cancel</string>
|
<string name="cancel">@android:string/cancel</string>
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
<string name="forward_10">forward 10 seconds</string>
|
<string name="forward_10">forward 10 seconds</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="next_episode">Next Episode</string>
|
||||||
|
|
||||||
<!-- dialogs -->
|
<!-- dialogs -->
|
||||||
<string name="save">save</string>
|
<string name="save">save</string>
|
||||||
|
|
Loading…
Reference in New Issue