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:
parent
bb8c8ca85a
commit
d01e87bf14
|
@ -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"),
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue