Player: add auto play next episode
This commit is contained in:
		| @ -8,6 +8,7 @@ import android.util.Log | ||||
| import android.view.* | ||||
| 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 | ||||
| @ -19,6 +20,7 @@ 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.* | ||||
| import kotlinx.coroutines.* | ||||
| import org.mosad.teapod.parser.AoDParser | ||||
| import org.mosad.teapod.preferences.Preferences | ||||
| import org.mosad.teapod.util.DataTypes.MediaType | ||||
| @ -33,8 +35,6 @@ class PlayerActivity : AppCompatActivity() { | ||||
|     private lateinit var controller: StyledPlayerControlView | ||||
|     private lateinit var gestureDetector: GestureDetectorCompat | ||||
|  | ||||
|     private var streamUrl = "" | ||||
|  | ||||
|     private var mediaId = 0 | ||||
|     private var episodeId = 0 | ||||
|  | ||||
| @ -119,19 +119,13 @@ class PlayerActivity : AppCompatActivity() { | ||||
|         initExoPlayer() | ||||
|         initVideoView() | ||||
|         initController() | ||||
|         initTimeUpdates() | ||||
|     } | ||||
|  | ||||
|     private fun initMedia() { | ||||
|         media = AoDParser.getMediaById(mediaId) | ||||
|         currentEpisode = media.episodes.first { it.id == episodeId } | ||||
|         streamUrlFromEp(currentEpisode) // get current stream | ||||
|  | ||||
|         // 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] | ||||
|         } | ||||
|         nextEpisode = selectNextEpisode() | ||||
|     } | ||||
|  | ||||
|     private fun initExoPlayer() { | ||||
| @ -140,7 +134,7 @@ class PlayerActivity : AppCompatActivity() { | ||||
|         controller = video_view.findViewById(R.id.exo_controller) | ||||
|  | ||||
|         val mediaSource = HlsMediaSource.Factory(dataSourceFactory) | ||||
|             .createMediaSource(MediaItem.fromUri(Uri.parse(streamUrl))) | ||||
|             .createMediaSource(MediaItem.fromUri(Uri.parse(selectStream(currentEpisode)))) | ||||
|  | ||||
|         player.playWhenReady = playWhenReady | ||||
|         player.setMediaSource(mediaSource) | ||||
| @ -162,6 +156,11 @@ class PlayerActivity : AppCompatActivity() { | ||||
|                     View.VISIBLE -> View.INVISIBLE | ||||
|                     else -> View.VISIBLE | ||||
|                 } | ||||
|  | ||||
|                 if (state == ExoPlayer.STATE_ENDED && nextEpisode != null) { | ||||
|                     playNextEpisode() | ||||
|                 } | ||||
|  | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| @ -198,6 +197,7 @@ class PlayerActivity : AppCompatActivity() { | ||||
|             } else { | ||||
|                 getString(R.string.time_hour_min_sec, hours, minutes, seconds) | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         exo_text_title.text = currentEpisode.title // set media title | ||||
| @ -207,6 +207,26 @@ class PlayerActivity : AppCompatActivity() { | ||||
|         exo_close_player.setOnClickListener { this.finish() } | ||||
|         exo_rew_10.setOnClickListener { rewind() } | ||||
|         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(){ | ||||
| @ -226,11 +246,23 @@ class PlayerActivity : AppCompatActivity() { | ||||
|         player.seekTo(player.currentPosition + fwdTime) | ||||
|     } | ||||
|  | ||||
|     @Suppress("unused") | ||||
|     private fun playNextEpisode() { | ||||
|         nextEpisode?.let { streamUrlFromEp(it) } | ||||
|         // TODO play | ||||
|         // TODO set next episode if present | ||||
|         nextEpisode?.let { nextEp -> | ||||
|             currentEpisode = nextEp // set current ep to next ep | ||||
|             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. | ||||
|      * If no stream is present, close the activity. | ||||
|      */ | ||||
|     private fun streamUrlFromEp(episode: Episode) { | ||||
|         streamUrl = if ((Preferences.preferSecondary || episode.priStreamUrl.isEmpty()) && episode.secStreamOmU) { | ||||
|     private fun selectStream(episode: Episode): String { | ||||
|         return if ((Preferences.preferSecondary || episode.priStreamUrl.isEmpty()) && episode.secStreamOmU) { | ||||
|             episode.secStreamUrl | ||||
|         } else if (episode.priStreamUrl.isNotEmpty()) { | ||||
|             episode.priStreamUrl | ||||
|         } else { | ||||
|             Log.e(javaClass.name, "No stream url set.") | ||||
|             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 | ||||
|  | ||||
| @ -73,6 +73,7 @@ object AoDParser { | ||||
|  | ||||
|             val resLogin = Jsoup.connect(baseUrl + loginPath) | ||||
|                 .method(Connection.Method.POST) | ||||
|                 .timeout(60000) // login can take some time | ||||
|                 .data(data) | ||||
|                 .postDataCharset("UTF-8") | ||||
|                 .cookies(authCookies) | ||||
|  | ||||
| @ -17,7 +17,6 @@ import kotlinx.android.synthetic.main.fragment_media.* | ||||
| import org.mosad.teapod.MainActivity | ||||
| import org.mosad.teapod.R | ||||
| import org.mosad.teapod.parser.AoDParser | ||||
| import org.mosad.teapod.preferences.Preferences | ||||
| import org.mosad.teapod.util.DataTypes.MediaType | ||||
| import org.mosad.teapod.util.Episode | ||||
| import org.mosad.teapod.util.Media | ||||
|  | ||||
| @ -24,6 +24,23 @@ | ||||
|         android:layout_width="70dp" | ||||
|         android:layout_height="70dp" | ||||
|         android:layout_gravity="center" | ||||
|         app:indicatorColor="@color/exo_white" | ||||
|         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> | ||||
| @ -35,6 +35,8 @@ | ||||
|     <string name="rewind_10">10 Sekunden zurück</string> | ||||
|     <string name="play_pause">Abspielen/Pause</string> | ||||
|     <string name="forward_10">10 Sekunden vorwärts</string> | ||||
|     <string name="next_episode">Nächste Episode</string> | ||||
|  | ||||
|     <!-- dialogs --> | ||||
|     <string name="save">speichern</string> | ||||
|     <string name="cancel">@android:string/cancel</string> | ||||
|  | ||||
| @ -46,6 +46,7 @@ | ||||
|     <string name="forward_10">forward 10 seconds</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="next_episode">Next Episode</string> | ||||
|  | ||||
|     <!-- dialogs --> | ||||
|     <string name="save">save</string> | ||||
|  | ||||
		Reference in New Issue
	
	Block a user