use suspending functions for coroutines when possible

* fix crash, when media is selected, but MediaFragment is removed before AoDParser could load data
This commit is contained in:
Jannik 2020-11-27 11:06:16 +01:00
parent bb8c8ca85a
commit d01e87bf14
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
4 changed files with 102 additions and 109 deletions

View File

@ -110,18 +110,18 @@ object AoDParser {
* get a media by it's ID (int) * get a media by it's ID (int)
* @return Media * @return Media
*/ */
fun getMediaById(mediaId: Int): Media { suspend fun getMediaById(mediaId: Int): Media {
val media = mediaList.first { it.id == mediaId } val media = mediaList.first { it.id == mediaId }
if (media.episodes.isEmpty()) { if (media.episodes.isEmpty()) {
loadStreams(media) loadStreams(media).join()
} }
return media return media
} }
// TODO don't use jsoup here // TODO don't use jsoup here
fun sendCallback(callbackPath: String) = GlobalScope.launch { fun sendCallback(callbackPath: String) = GlobalScope.launch(Dispatchers.IO) {
val headers = mutableMapOf( val headers = mutableMapOf(
Pair("Accept", "application/json, text/javascript, */*; q=0.01"), Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"), Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
@ -131,13 +131,11 @@ object AoDParser {
) )
try { try {
withContext(Dispatchers.IO) { Jsoup.connect(baseUrl + callbackPath)
Jsoup.connect(baseUrl + callbackPath) .ignoreContentType(true)
.ignoreContentType(true) .cookies(sessionCookies)
.cookies(sessionCookies) .headers(headers)
.headers(headers) .execute()
.execute()
}
} catch (ex: IOException) { } catch (ex: IOException) {
Log.e(javaClass.name, "Callback for $callbackPath failed.", ex) Log.e(javaClass.name, "Callback for $callbackPath failed.", ex)
} }
@ -213,38 +211,62 @@ object AoDParser {
* load streams for the media path, movies have one episode * load streams for the media path, movies have one episode
* @param media is used as call ba reference * @param media is used as call ba reference
*/ */
private fun loadStreams(media: Media) = runBlocking { private suspend fun loadStreams(media: Media) = GlobalScope.launch(Dispatchers.IO) {
if (sessionCookies.isEmpty()) login() if (sessionCookies.isEmpty()) login()
if (!loginSuccess) { if (!loginSuccess) {
Log.w(javaClass.name, "Login, was not successful.") Log.w(javaClass.name, "Login, was not successful.")
return@runBlocking return@launch
} }
withContext(Dispatchers.Default) { // get the media page
val res = Jsoup.connect(baseUrl + media.link)
.cookies(sessionCookies)
.get()
// get the media page //println(res)
val res = Jsoup.connect(baseUrl + media.link)
.cookies(sessionCookies)
.get()
//println(res) if (csrfToken.isEmpty()) {
csrfToken = res.select("meta[name=csrf-token]").attr("content")
//Log.i(javaClass.name, "New csrf token is $csrfToken")
}
if (csrfToken.isEmpty()) { val pl = res.select("input.streamstarter_html5").first()
csrfToken = res.select("meta[name=csrf-token]").attr("content") val primary = pl.attr("data-playlist")
//Log.i(javaClass.name, "New csrf token is $csrfToken") val secondary = pl.attr("data-otherplaylist")
val secondaryIsOmU = secondary.contains("OmU", true)
// load primary and secondary playlist
val primaryPlaylist = parsePlaylistAsync(primary)
val secondaryPlaylist = parsePlaylistAsync(secondary)
primaryPlaylist.await().playlist.forEach { ep ->
val epNumber = if (media.type == MediaType.TVSHOW) {
ep.title.substringAfter(", Ep. ").toInt()
} else {
0
} }
val pl = res.select("input.streamstarter_html5").first() media.episodes.add(
val primary = pl.attr("data-playlist") Episode(
val secondary = pl.attr("data-otherplaylist") id = ep.mediaid,
val secondaryIsOmU = secondary.contains("OmU", true) priStreamUrl = ep.sources.first().file,
posterUrl = ep.image,
title = ep.title,
description = ep.description,
number = epNumber
)
)
}
Log.i(javaClass.name, "Loading primary playlist finished")
// load primary and secondary playlist secondaryPlaylist.await().playlist.forEach { ep ->
val primaryPlaylist = parsePlaylistAsync(primary) val episode = media.episodes.firstOrNull { it.id == ep.mediaid }
val secondaryPlaylist = parsePlaylistAsync(secondary)
primaryPlaylist.await().playlist.forEach { ep -> if (episode != null) {
episode.secStreamUrl = ep.sources.first().file
episode.secStreamOmU = secondaryIsOmU
} else {
val epNumber = if (media.type == MediaType.TVSHOW) { val epNumber = if (media.type == MediaType.TVSHOW) {
ep.title.substringAfter(", Ep. ").toInt() ep.title.substringAfter(", Ep. ").toInt()
} else { } else {
@ -254,7 +276,8 @@ object AoDParser {
media.episodes.add( media.episodes.add(
Episode( Episode(
id = ep.mediaid, id = ep.mediaid,
priStreamUrl = ep.sources.first().file, secStreamUrl = ep.sources.first().file,
secStreamOmU = secondaryIsOmU,
posterUrl = ep.image, posterUrl = ep.image,
title = ep.title, title = ep.title,
description = ep.description, description = ep.description,
@ -262,69 +285,40 @@ object AoDParser {
) )
) )
} }
Log.i(javaClass.name, "Loading primary playlist finished") }
Log.i(javaClass.name, "Loading secondary playlist finished")
secondaryPlaylist.await().playlist.forEach { ep -> // parse additional info from the media page
val episode = media.episodes.firstOrNull { it.id == ep.mediaid } res.select("table.vertical-table").select("tr").forEach { row ->
when (row.select("th").text().toLowerCase(Locale.ROOT)) {
if (episode != null) { "produktionsjahr" -> media.info.year = row.select("td").text().toInt()
episode.secStreamUrl = ep.sources.first().file "fsk" -> media.info.age = row.select("td").text().toInt()
episode.secStreamOmU = secondaryIsOmU "episodenanzahl" -> {
} else { media.info.episodesCount = row.select("td").text()
val epNumber = if (media.type == MediaType.TVSHOW) { .substringBefore("/")
ep.title.substringAfter(", Ep. ").toInt() .filter { it.isDigit() }
} else { .toInt()
0
}
media.episodes.add(
Episode(
id = ep.mediaid,
secStreamUrl = ep.sources.first().file,
secStreamOmU = secondaryIsOmU,
posterUrl = ep.image,
title = ep.title,
description = ep.description,
number = epNumber
)
)
} }
} }
Log.i(javaClass.name, "Loading secondary playlist finished") }
// parse additional info from the media page // parse additional information for tv shows
res.select("table.vertical-table").select("tr").forEach { row -> if (media.type == MediaType.TVSHOW) {
when (row.select("th").text().toLowerCase(Locale.ROOT)) { res.select("div.three-box-container > div.episodebox").forEach { episodebox ->
"produktionsjahr" -> media.info.year = row.select("td").text().toInt() // make sure the episode has a streaming link
"fsk" -> media.info.age = row.select("td").text().toInt() if (episodebox.select("input.streamstarter_html5").isNotEmpty()) {
"episodenanzahl" -> { val episodeId = episodebox.select("div.flip-front").attr("id").substringAfter("-").toInt()
media.info.episodesCount = row.select("td").text() val episodeShortDesc = episodebox.select("p.episodebox-shorttext").text()
.substringBefore("/") val episodeWatched = episodebox.select("div.episodebox-icons > div").hasClass("status-icon-orange")
.filter{ it.isDigit() } val episodeWatchedCallback = episodebox.select("input.streamstarter_html5").eachAttr("data-playlist").first()
.toInt()
media.episodes.firstOrNull { it.id == episodeId }?.apply {
shortDesc = episodeShortDesc
watched = episodeWatched
watchedCallback = episodeWatchedCallback
} }
} }
} }
// parse additional information for tv shows
if (media.type == MediaType.TVSHOW) {
res.select("div.three-box-container > div.episodebox").forEach { episodebox ->
// make sure the episode has a streaming link
if (episodebox.select("input.streamstarter_html5").isNotEmpty()) {
val episodeId = episodebox.select("div.flip-front").attr("id").substringAfter("-").toInt()
val episodeShortDesc = episodebox.select("p.episodebox-shorttext").text()
val episodeWatched = episodebox.select("div.episodebox-icons > div").hasClass("status-icon-orange")
val episodeWatchedCallback = episodebox.select("input.streamstarter_html5").eachAttr("data-playlist").first()
media.episodes.firstOrNull { it.id == episodeId }?.apply {
shortDesc = episodeShortDesc
watched = episodeWatched
watchedCallback = episodeWatchedCallback
}
}
}
}
} }
} }
@ -336,7 +330,7 @@ object AoDParser {
return CompletableDeferred(AoDObject(listOf())) return CompletableDeferred(AoDObject(listOf()))
} }
return GlobalScope.async { return GlobalScope.async(Dispatchers.IO) {
val headers = mutableMapOf( val headers = mutableMapOf(
Pair("Accept", "application/json, text/javascript, */*; q=0.01"), Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"), Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),

View File

@ -1,6 +1,7 @@
package org.mosad.teapod.player package org.mosad.teapod.player
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import kotlinx.coroutines.runBlocking
import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.parser.AoDParser
import org.mosad.teapod.ui.fragments.MediaFragment import org.mosad.teapod.ui.fragments.MediaFragment
import org.mosad.teapod.util.DataTypes import org.mosad.teapod.util.DataTypes
@ -25,7 +26,10 @@ class PlayerViewModel : ViewModel() {
mediaId = iMediaId mediaId = iMediaId
episodeId = iEpisodeId episodeId = iEpisodeId
media = AoDParser.getMediaById(mediaId) runBlocking {
media = AoDParser.getMediaById(mediaId)
}
currentEpisode = media.episodes.first { it.id == episodeId } currentEpisode = media.episodes.first { it.id == episodeId }
nextEpisode = selectNextEpisode() nextEpisode = selectNextEpisode()
} }

View File

@ -49,12 +49,12 @@ class MediaFragment(private val mediaId: Int) : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.frameLoading.visibility = View.VISIBLE binding.frameLoading.visibility = View.VISIBLE
GlobalScope.launch { GlobalScope.launch(Dispatchers.Main) {
// load the streams for the selected media // load the streams for the selected media
media = AoDParser.getMediaById(mediaId) media = AoDParser.getMediaById(mediaId)
tmdb = TMDBApiController().search(media.info.title, media.type) tmdb = TMDBApiController().search(media.info.title, media.type)
withContext(Dispatchers.Main) { if (this@MediaFragment.isAdded) {
updateGUI() updateGUI()
initActions() initActions()
} }

View File

@ -3,9 +3,7 @@ package org.mosad.teapod.util
import android.util.Log import android.util.Log
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser import com.google.gson.JsonParser
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.*
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import java.net.URL import java.net.URL
import java.net.URLEncoder import java.net.URLEncoder
import org.mosad.teapod.util.DataTypes.MediaType import org.mosad.teapod.util.DataTypes.MediaType
@ -22,12 +20,12 @@ class TMDBApiController {
private val imageUrl = "https://image.tmdb.org/t/p/w500" private val imageUrl = "https://image.tmdb.org/t/p/w500"
fun search(title: String, type: MediaType): TMDBResponse { suspend fun search(title: String, type: MediaType): TMDBResponse {
val searchTerm = title.replace("(Sub)", "").trim() val searchTerm = title.replace("(Sub)", "").trim()
return when (type) { return when (type) {
MediaType.MOVIE -> searchMovie(searchTerm) MediaType.MOVIE -> searchMovie(searchTerm).await()
MediaType.TVSHOW -> searchTVShow(searchTerm) MediaType.TVSHOW -> searchTVShow(searchTerm).await()
else -> { else -> {
Log.e(javaClass.name, "Wrong Type: $type") Log.e(javaClass.name, "Wrong Type: $type")
TMDBResponse() TMDBResponse()
@ -36,17 +34,17 @@ class TMDBApiController {
} }
fun searchTVShow(title: String) = runBlocking { fun searchTVShow(title: String): Deferred<TMDBResponse> {
val url = URL("$searchTVUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}") val url = URL("$searchTVUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
GlobalScope.async { return GlobalScope.async {
val response = JsonParser.parseString(url.readText()).asJsonObject val response = JsonParser.parseString(url.readText()).asJsonObject
//println(response) //println(response)
return@async if (response.get("total_results").asInt > 0) { if (response.get("total_results").asInt > 0) {
response.get("results").asJsonArray.first().asJsonObject.let { response.get("results").asJsonArray.first().asJsonObject.let {
val id = getStringNotNull(it,"id").toInt() val id = getStringNotNull(it, "id").toInt()
val overview = getStringNotNull(it,"overview") val overview = getStringNotNull(it, "overview")
val posterPath = getStringNotNullPrefix(it, "poster_path", imageUrl) val posterPath = getStringNotNullPrefix(it, "poster_path", imageUrl)
val backdropPath = getStringNotNullPrefix(it, "backdrop_path", imageUrl) val backdropPath = getStringNotNullPrefix(it, "backdrop_path", imageUrl)
@ -55,18 +53,17 @@ class TMDBApiController {
} else { } else {
TMDBResponse() TMDBResponse()
} }
}.await() }
} }
fun searchMovie(title: String) = runBlocking { fun searchMovie(title: String): Deferred<TMDBResponse> {
val url = URL("$searchMovieUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}") val url = URL("$searchMovieUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
GlobalScope.async { return GlobalScope.async {
val response = JsonParser.parseString(url.readText()).asJsonObject val response = JsonParser.parseString(url.readText()).asJsonObject
//println(response) //println(response)
return@async if (response.get("total_results").asInt > 0) { if (response.get("total_results").asInt > 0) {
response.get("results").asJsonArray.first().asJsonObject.let { response.get("results").asJsonArray.first().asJsonObject.let {
val id = getStringNotNull(it,"id").toInt() val id = getStringNotNull(it,"id").toInt()
val overview = getStringNotNull(it,"overview") val overview = getStringNotNull(it,"overview")
@ -79,9 +76,7 @@ class TMDBApiController {
} else { } else {
TMDBResponse() TMDBResponse()
} }
}
}.await()
} }
/** /**