update some libraries & coroutines 1.5.0
* androidx.core 1.3.2 -> 1.5.0 * androidx.appcompat 1.2.0 -> 1.3.0 * gson 2.8.6 -> 2.8.7 * coroutines-android 1.4.3 -> 1.5.0 * don't use GlobalScope, use lifecycleScope and vieModelScope instead. This fixes a few issues when fragments where destroied befor the coroutine finished. * gradle wrapper 7.0 -> 7.9.2
This commit is contained in:
parent
46e3d1f1b6
commit
5e48e724a7
|
@ -41,18 +41,20 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.3.2'
|
implementation 'androidx.core:core-ktx:1.5.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
|
||||||
implementation 'androidx.security:security-crypto:1.1.0-alpha03'
|
implementation 'androidx.security:security-crypto:1.1.0-alpha03'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
|
||||||
|
|
||||||
implementation 'com.google.android.material:material:1.3.0'
|
implementation 'com.google.android.material:material:1.3.0'
|
||||||
implementation 'com.google.code.gson:gson:2.8.6'
|
implementation 'com.google.code.gson:gson:2.8.7'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.13.3'
|
implementation 'com.google.android.exoplayer:exoplayer-core:2.13.3'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-hls:2.13.3'
|
implementation 'com.google.android.exoplayer:exoplayer-hls:2.13.3'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-dash:2.13.3'
|
implementation 'com.google.android.exoplayer:exoplayer-dash:2.13.3'
|
||||||
|
|
|
@ -99,10 +99,12 @@ object AoDParser {
|
||||||
/**
|
/**
|
||||||
* initially load all media and home screen data
|
* initially load all media and home screen data
|
||||||
*/
|
*/
|
||||||
fun initialLoading() = listOf(
|
suspend fun initialLoading() {
|
||||||
loadHome(),
|
coroutineScope {
|
||||||
listAnimes()
|
launch { loadHome() }
|
||||||
)
|
launch { listAnimes() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get a media by it's ID (int)
|
* get a media by it's ID (int)
|
||||||
|
@ -121,15 +123,16 @@ object AoDParser {
|
||||||
/**
|
/**
|
||||||
* get subscription info from aod website, remove "Anime-Abo" Prefix and trim
|
* get subscription info from aod website, remove "Anime-Abo" Prefix and trim
|
||||||
*/
|
*/
|
||||||
fun getSubscriptionInfoAsync(): Deferred<String> {
|
suspend fun getSubscriptionInfoAsync(): Deferred<String> {
|
||||||
return GlobalScope.async(Dispatchers.IO) {
|
return coroutineScope {
|
||||||
// get the subscription page
|
async(Dispatchers.IO) {
|
||||||
val res = Jsoup.connect(baseUrl + subscriptionPath)
|
val res = Jsoup.connect(baseUrl + subscriptionPath)
|
||||||
.cookies(sessionCookies)
|
.cookies(sessionCookies)
|
||||||
.get()
|
.get()
|
||||||
|
|
||||||
return@async res.select("a:contains(Anime-Abo)").text()
|
return@async res.select("a:contains(Anime-Abo)").text()
|
||||||
.removePrefix("Anime-Abo").trim()
|
.removePrefix("Anime-Abo").trim()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +140,7 @@ object AoDParser {
|
||||||
return baseUrl + subscriptionPath
|
return baseUrl + subscriptionPath
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markAsWatched(mediaId: Int, episodeId: Int) = GlobalScope.launch {
|
suspend fun markAsWatched(mediaId: Int, episodeId: Int) {
|
||||||
val episode = getMediaById(mediaId).getEpisodeById(episodeId)
|
val episode = getMediaById(mediaId).getEpisodeById(episodeId)
|
||||||
episode.watched = true
|
episode.watched = true
|
||||||
sendCallback(episode.watchedCallback)
|
sendCallback(episode.watchedCallback)
|
||||||
|
@ -146,137 +149,145 @@ object AoDParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO don't use jsoup here
|
// TODO don't use jsoup here
|
||||||
private fun sendCallback(callbackPath: String) = GlobalScope.launch(Dispatchers.IO) {
|
private suspend fun sendCallback(callbackPath: String) = coroutineScope {
|
||||||
val headers = mutableMapOf(
|
launch(Dispatchers.IO) {
|
||||||
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
|
val headers = mutableMapOf(
|
||||||
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
|
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
|
||||||
Pair("Accept-Encoding", "gzip, deflate, br"),
|
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
|
||||||
Pair("X-CSRF-Token", csrfToken),
|
Pair("Accept-Encoding", "gzip, deflate, br"),
|
||||||
Pair("X-Requested-With", "XMLHttpRequest"),
|
Pair("X-CSRF-Token", csrfToken),
|
||||||
)
|
Pair("X-Requested-With", "XMLHttpRequest"),
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* load all media from aod into itemMediaList and mediaList
|
* load all media from aod into itemMediaList and mediaList
|
||||||
|
* TODO private suspend fun listAnimes() = withContext(Dispatchers.IO) should also work, maybe a bug in android studio?
|
||||||
*/
|
*/
|
||||||
private fun listAnimes() = GlobalScope.launch(Dispatchers.IO) {
|
private suspend fun listAnimes() = withContext(Dispatchers.IO) {
|
||||||
val resAnimes = Jsoup.connect(baseUrl + libraryPath).get()
|
launch(Dispatchers.IO) {
|
||||||
//println(resAnimes)
|
val resAnimes = Jsoup.connect(baseUrl + libraryPath).get()
|
||||||
|
//println(resAnimes)
|
||||||
|
|
||||||
itemMediaList.clear()
|
itemMediaList.clear()
|
||||||
mediaList.clear()
|
mediaList.clear()
|
||||||
resAnimes.select("div.animebox").forEach {
|
resAnimes.select("div.animebox").forEach {
|
||||||
val type = if (it.select("p.animebox-link").select("a").text().lowercase(Locale.ROOT) == "zur serie") {
|
val type = if (it.select("p.animebox-link").select("a").text().lowercase(Locale.ROOT) == "zur serie") {
|
||||||
MediaType.TVSHOW
|
MediaType.TVSHOW
|
||||||
} else {
|
} else {
|
||||||
MediaType.MOVIE
|
MediaType.MOVIE
|
||||||
|
}
|
||||||
|
val mediaTitle = it.select("h3.animebox-title").text()
|
||||||
|
val mediaLink = it.select("p.animebox-link").select("a").attr("href")
|
||||||
|
val mediaImage = it.select("p.animebox-image").select("img").attr("src")
|
||||||
|
val mediaShortText = it.select("p.animebox-shorttext").text()
|
||||||
|
val mediaId = mediaLink.substringAfterLast("/").toInt()
|
||||||
|
|
||||||
|
itemMediaList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
||||||
|
mediaList.add(Media(mediaId, mediaLink, type).apply {
|
||||||
|
info.title = mediaTitle
|
||||||
|
info.posterUrl = mediaImage
|
||||||
|
info.shortDesc = mediaShortText
|
||||||
|
})
|
||||||
}
|
}
|
||||||
val mediaTitle = it.select("h3.animebox-title").text()
|
|
||||||
val mediaLink = it.select("p.animebox-link").select("a").attr("href")
|
|
||||||
val mediaImage = it.select("p.animebox-image").select("img").attr("src")
|
|
||||||
val mediaShortText = it.select("p.animebox-shorttext").text()
|
|
||||||
val mediaId = mediaLink.substringAfterLast("/").toInt()
|
|
||||||
|
|
||||||
itemMediaList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
Log.i(javaClass.name, "Total library size is: ${mediaList.size}")
|
||||||
mediaList.add(Media(mediaId, mediaLink, type).apply {
|
|
||||||
info.title = mediaTitle
|
|
||||||
info.posterUrl = mediaImage
|
|
||||||
info.shortDesc = mediaShortText
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(javaClass.name, "Total library size is: ${mediaList.size}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* load new episodes, titles and highlights
|
* load new episodes, titles and highlights
|
||||||
*/
|
*/
|
||||||
private fun loadHome() = GlobalScope.launch(Dispatchers.IO) {
|
private suspend fun loadHome() = withContext(Dispatchers.IO) {
|
||||||
val resHome = Jsoup.connect(baseUrl).get()
|
launch(Dispatchers.IO) {
|
||||||
|
val resHome = Jsoup.connect(baseUrl).get()
|
||||||
|
|
||||||
// get highlights from AoD
|
// get highlights from AoD
|
||||||
highlightsList.clear()
|
highlightsList.clear()
|
||||||
resHome.select("#aod-highlights").select("div.news-item").forEach {
|
resHome.select("#aod-highlights").select("div.news-item").forEach {
|
||||||
val mediaId = it.select("div.news-item-text").select("a.serienlink")
|
val mediaId = it.select("div.news-item-text").select("a.serienlink")
|
||||||
.attr("href").substringAfterLast("/").toIntOrNull()
|
.attr("href").substringAfterLast("/").toIntOrNull()
|
||||||
val mediaTitle = it.select("div.news-title").select("h2").text()
|
val mediaTitle = it.select("div.news-title").select("h2").text()
|
||||||
val mediaImage = it.select("img").attr("src")
|
val mediaImage = it.select("img").attr("src")
|
||||||
|
|
||||||
if (mediaId != null) {
|
if (mediaId != null) {
|
||||||
highlightsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
highlightsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// get all new episodes from AoD
|
// get all new episodes from AoD
|
||||||
newEpisodesList.clear()
|
newEpisodesList.clear()
|
||||||
resHome.select("h2:contains(Neue Episoden)").next().select("li").forEach {
|
resHome.select("h2:contains(Neue Episoden)").next().select("li").forEach {
|
||||||
val mediaId = it.select("a.thumbs").attr("href")
|
val mediaId = it.select("a.thumbs").attr("href")
|
||||||
.substringAfterLast("/").toIntOrNull()
|
.substringAfterLast("/").toIntOrNull()
|
||||||
val mediaImage = it.select("a.thumbs > img").attr("src")
|
val mediaImage = it.select("a.thumbs > img").attr("src")
|
||||||
val mediaTitle = "${it.select("a").text()} - ${it.select("span.neweps").text()}"
|
val mediaTitle = "${it.select("a").text()} - ${it.select("span.neweps").text()}"
|
||||||
|
|
||||||
if (mediaId != null) {
|
if (mediaId != null) {
|
||||||
newEpisodesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
newEpisodesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// get new simulcasts from AoD
|
// get new simulcasts from AoD
|
||||||
newSimulcastsList.clear()
|
newSimulcastsList.clear()
|
||||||
resHome.select("h2:contains(Neue Simulcasts)").next().select("li").forEach {
|
resHome.select("h2:contains(Neue Simulcasts)").next().select("li").forEach {
|
||||||
val mediaId = it.select("a.thumbs").attr("href")
|
val mediaId = it.select("a.thumbs").attr("href")
|
||||||
.substringAfterLast("/").toIntOrNull()
|
.substringAfterLast("/").toIntOrNull()
|
||||||
val mediaImage = it.select("a.thumbs > img").attr("src")
|
val mediaImage = it.select("a.thumbs > img").attr("src")
|
||||||
val mediaTitle = it.select("a").text()
|
val mediaTitle = it.select("a").text()
|
||||||
|
|
||||||
if (mediaId != null) {
|
if (mediaId != null) {
|
||||||
newSimulcastsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
newSimulcastsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// get new titles from AoD
|
// get new titles from AoD
|
||||||
newTitlesList.clear()
|
newTitlesList.clear()
|
||||||
resHome.select("h2:contains(Neue Anime-Titel)").next().select("li").forEach {
|
resHome.select("h2:contains(Neue Anime-Titel)").next().select("li").forEach {
|
||||||
val mediaId = it.select("a.thumbs").attr("href")
|
val mediaId = it.select("a.thumbs").attr("href")
|
||||||
.substringAfterLast("/").toIntOrNull()
|
.substringAfterLast("/").toIntOrNull()
|
||||||
val mediaImage = it.select("a.thumbs > img").attr("src")
|
val mediaImage = it.select("a.thumbs > img").attr("src")
|
||||||
val mediaTitle = it.select("a").text()
|
val mediaTitle = it.select("a").text()
|
||||||
|
|
||||||
if (mediaId != null) {
|
if (mediaId != null) {
|
||||||
newTitlesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
newTitlesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// get top ten from AoD
|
// get top ten from AoD
|
||||||
topTenList.clear()
|
topTenList.clear()
|
||||||
resHome.select("h2:contains(Anime Top 10)").next().select("li").forEach {
|
resHome.select("h2:contains(Anime Top 10)").next().select("li").forEach {
|
||||||
val mediaId = it.select("a.thumbs").attr("href")
|
val mediaId = it.select("a.thumbs").attr("href")
|
||||||
.substringAfterLast("/").toIntOrNull()
|
.substringAfterLast("/").toIntOrNull()
|
||||||
val mediaImage = it.select("a.thumbs > img").attr("src")
|
val mediaImage = it.select("a.thumbs > img").attr("src")
|
||||||
val mediaTitle = it.select("a").text()
|
val mediaTitle = it.select("a").text()
|
||||||
|
|
||||||
if (mediaId != null) {
|
if (mediaId != null) {
|
||||||
topTenList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
topTenList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// if highlights is empty, add a random new title
|
// if highlights is empty, add a random new title
|
||||||
if (highlightsList.isEmpty()) {
|
if (highlightsList.isEmpty()) {
|
||||||
if (newTitlesList.isNotEmpty()) {
|
if (newTitlesList.isNotEmpty()) {
|
||||||
highlightsList.add(newTitlesList[Random.nextInt(0, newTitlesList.size)])
|
highlightsList.add(newTitlesList[Random.nextInt(0, newTitlesList.size)])
|
||||||
} else {
|
} else {
|
||||||
highlightsList.add(ItemMedia(0,"", ""))
|
highlightsList.add(ItemMedia(0,"", ""))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.i(javaClass.name, "loaded home")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,112 +297,114 @@ 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) = GlobalScope.launch(Dispatchers.IO) {
|
private suspend fun loadStreams(media: Media) = coroutineScope {
|
||||||
if (sessionCookies.isEmpty()) login()
|
launch(Dispatchers.IO) {
|
||||||
|
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@launch
|
return@launch
|
||||||
}
|
|
||||||
|
|
||||||
// get the media page
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
val besides = res.select("div.besides").first()
|
|
||||||
val playlists = besides.select("input.streamstarter_html5").map { streamstarter ->
|
|
||||||
parsePlaylistAsync(
|
|
||||||
streamstarter.attr("data-playlist"),
|
|
||||||
streamstarter.attr("data-lang")
|
|
||||||
)
|
|
||||||
}.awaitAll()
|
|
||||||
|
|
||||||
playlists.forEach { aod ->
|
|
||||||
// TODO improve language handling
|
|
||||||
val locale = when (aod.extLanguage) {
|
|
||||||
"ger" -> Locale.GERMAN
|
|
||||||
"jap" -> Locale.JAPANESE
|
|
||||||
else -> Locale.ROOT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aod.playlist.forEach { ep ->
|
// get the media page
|
||||||
try {
|
val res = Jsoup.connect(baseUrl + media.link)
|
||||||
if (media.hasEpisode(ep.mediaid)) {
|
.cookies(sessionCookies)
|
||||||
media.getEpisodeById(ep.mediaid).streams.add(
|
.get()
|
||||||
Stream(ep.sources.first().file, locale)
|
|
||||||
)
|
//println(res)
|
||||||
} else {
|
|
||||||
media.episodes.add(Episode(
|
if (csrfToken.isEmpty()) {
|
||||||
id = ep.mediaid,
|
csrfToken = res.select("meta[name=csrf-token]").attr("content")
|
||||||
streams = mutableListOf(Stream(ep.sources.first().file, locale)),
|
//Log.i(javaClass.name, "New csrf token is $csrfToken")
|
||||||
posterUrl = ep.image,
|
}
|
||||||
title = ep.title,
|
|
||||||
description = ep.description,
|
val besides = res.select("div.besides").first()
|
||||||
number = getNumberFromTitle(ep.title, media.type)
|
val playlists = besides.select("input.streamstarter_html5").map { streamstarter ->
|
||||||
))
|
parsePlaylistAsync(
|
||||||
}
|
streamstarter.attr("data-playlist"),
|
||||||
} catch (ex: Exception) {
|
streamstarter.attr("data-lang")
|
||||||
Log.w(javaClass.name, "Could not parse episode information.", ex)
|
)
|
||||||
|
}.awaitAll()
|
||||||
|
|
||||||
|
playlists.forEach { aod ->
|
||||||
|
// TODO improve language handling
|
||||||
|
val locale = when (aod.extLanguage) {
|
||||||
|
"ger" -> Locale.GERMAN
|
||||||
|
"jap" -> Locale.JAPANESE
|
||||||
|
else -> Locale.ROOT
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.i(javaClass.name, "Loaded playlists successfully")
|
|
||||||
|
|
||||||
// additional info from the media page
|
aod.playlist.forEach { ep ->
|
||||||
res.select("table.vertical-table").select("tr").forEach { row ->
|
try {
|
||||||
when (row.select("th").text().lowercase(Locale.ROOT)) {
|
if (media.hasEpisode(ep.mediaid)) {
|
||||||
"produktionsjahr" -> media.info.year = row.select("td").text().toInt()
|
media.getEpisodeById(ep.mediaid).streams.add(
|
||||||
"fsk" -> media.info.age = row.select("td").text().toInt()
|
Stream(ep.sources.first().file, locale)
|
||||||
"episodenanzahl" -> {
|
)
|
||||||
media.info.episodesCount = row.select("td").text()
|
} else {
|
||||||
.substringBefore("/")
|
media.episodes.add(Episode(
|
||||||
.filter { it.isDigit() }
|
id = ep.mediaid,
|
||||||
.toInt()
|
streams = mutableListOf(Stream(ep.sources.first().file, locale)),
|
||||||
}
|
posterUrl = ep.image,
|
||||||
}
|
title = ep.title,
|
||||||
}
|
description = ep.description,
|
||||||
|
number = getNumberFromTitle(ep.title, media.type)
|
||||||
// similar titles from media page
|
))
|
||||||
media.info.similar = res.select("h2:contains(Ähnliche Animes)").next().select("li").mapNotNull {
|
}
|
||||||
val mediaId = it.select("a.thumbs").attr("href")
|
} catch (ex: Exception) {
|
||||||
.substringAfterLast("/").toIntOrNull()
|
Log.w(javaClass.name, "Could not parse episode information.", ex)
|
||||||
val mediaImage = it.select("a.thumbs > img").attr("src")
|
|
||||||
val mediaTitle = it.select("a").text()
|
|
||||||
|
|
||||||
if (mediaId != null) {
|
|
||||||
ItemMedia(mediaId, mediaTitle, mediaImage)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// additional information for tv shows the episode title (description) is loaded from the "api"
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Log.i(javaClass.name, "Loaded playlists successfully")
|
||||||
|
|
||||||
|
// additional info from the media page
|
||||||
|
res.select("table.vertical-table").select("tr").forEach { row ->
|
||||||
|
when (row.select("th").text().lowercase(Locale.ROOT)) {
|
||||||
|
"produktionsjahr" -> media.info.year = row.select("td").text().toInt()
|
||||||
|
"fsk" -> media.info.age = row.select("td").text().toInt()
|
||||||
|
"episodenanzahl" -> {
|
||||||
|
media.info.episodesCount = row.select("td").text()
|
||||||
|
.substringBefore("/")
|
||||||
|
.filter { it.isDigit() }
|
||||||
|
.toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// similar titles from media page
|
||||||
|
media.info.similar = res.select("h2:contains(Ähnliche Animes)").next().select("li").mapNotNull {
|
||||||
|
val mediaId = it.select("a.thumbs").attr("href")
|
||||||
|
.substringAfterLast("/").toIntOrNull()
|
||||||
|
val mediaImage = it.select("a.thumbs > img").attr("src")
|
||||||
|
val mediaTitle = it.select("a").text()
|
||||||
|
|
||||||
|
if (mediaId != null) {
|
||||||
|
ItemMedia(mediaId, mediaTitle, mediaImage)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// additional information for tv shows the episode title (description) is loaded from the "api"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(javaClass.name, "media loaded successfully")
|
||||||
}
|
}
|
||||||
Log.i(javaClass.name, "media loaded successfully")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -402,7 +415,7 @@ object AoDParser {
|
||||||
return CompletableDeferred(AoDObject(listOf(), language))
|
return CompletableDeferred(AoDObject(listOf(), language))
|
||||||
}
|
}
|
||||||
|
|
||||||
return GlobalScope.async(Dispatchers.IO) {
|
return CoroutineScope(Dispatchers.IO).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"),
|
||||||
|
|
|
@ -32,20 +32,19 @@ import androidx.fragment.app.commit
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.callbacks.onDismiss
|
import com.afollestad.materialdialogs.callbacks.onDismiss
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import kotlinx.coroutines.joinAll
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
import org.mosad.teapod.databinding.ActivityMainBinding
|
import org.mosad.teapod.databinding.ActivityMainBinding
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
import org.mosad.teapod.ui.activity.player.PlayerActivity
|
|
||||||
import org.mosad.teapod.preferences.EncryptedPreferences
|
import org.mosad.teapod.preferences.EncryptedPreferences
|
||||||
import org.mosad.teapod.preferences.Preferences
|
import org.mosad.teapod.preferences.Preferences
|
||||||
import org.mosad.teapod.ui.components.LoginDialog
|
|
||||||
import org.mosad.teapod.ui.activity.main.fragments.AccountFragment
|
import org.mosad.teapod.ui.activity.main.fragments.AccountFragment
|
||||||
import org.mosad.teapod.ui.activity.main.fragments.HomeFragment
|
import org.mosad.teapod.ui.activity.main.fragments.HomeFragment
|
||||||
import org.mosad.teapod.ui.activity.main.fragments.LibraryFragment
|
import org.mosad.teapod.ui.activity.main.fragments.LibraryFragment
|
||||||
import org.mosad.teapod.ui.activity.main.fragments.SearchFragment
|
import org.mosad.teapod.ui.activity.main.fragments.SearchFragment
|
||||||
import org.mosad.teapod.ui.activity.onboarding.OnboardingActivity
|
import org.mosad.teapod.ui.activity.onboarding.OnboardingActivity
|
||||||
|
import org.mosad.teapod.ui.activity.player.PlayerActivity
|
||||||
|
import org.mosad.teapod.ui.components.LoginDialog
|
||||||
import org.mosad.teapod.util.DataTypes
|
import org.mosad.teapod.util.DataTypes
|
||||||
import org.mosad.teapod.util.StorageController
|
import org.mosad.teapod.util.StorageController
|
||||||
import org.mosad.teapod.util.exitAndRemoveTask
|
import org.mosad.teapod.util.exitAndRemoveTask
|
||||||
|
@ -138,7 +137,8 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
|
||||||
*/
|
*/
|
||||||
private fun load() {
|
private fun load() {
|
||||||
val time = measureTimeMillis {
|
val time = measureTimeMillis {
|
||||||
val loadingJob = AoDParser.initialLoading() // start the initial loading
|
val loadingJob = CoroutineScope(Dispatchers.IO + CoroutineName("InitialLoadingScope"))
|
||||||
|
.async { AoDParser.initialLoading() } // start the initial loading
|
||||||
|
|
||||||
// load all saved stuff here
|
// load all saved stuff here
|
||||||
Preferences.load(this)
|
Preferences.load(this)
|
||||||
|
@ -165,7 +165,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runBlocking { loadingJob.joinAll() } // wait for initial loading to finish
|
runBlocking { loadingJob.await() } // wait for initial loading to finish
|
||||||
}
|
}
|
||||||
Log.i(javaClass.name, "loading and login in $time ms")
|
Log.i(javaClass.name, "loading and login in $time ms")
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.mosad.teapod.BuildConfig
|
import org.mosad.teapod.BuildConfig
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
|
@ -64,7 +64,7 @@ class AccountFragment : Fragment() {
|
||||||
|
|
||||||
// load subscription (async) info before anything else
|
// load subscription (async) info before anything else
|
||||||
binding.textAccountSubscription.text = getString(R.string.account_subscription, getString(R.string.loading))
|
binding.textAccountSubscription.text = getString(R.string.account_subscription, getString(R.string.loading))
|
||||||
GlobalScope.launch {
|
lifecycleScope.launch {
|
||||||
binding.textAccountSubscription.text = getString(
|
binding.textAccountSubscription.text = getString(
|
||||||
R.string.account_subscription,
|
R.string.account_subscription,
|
||||||
AoDParser.getSubscriptionInfoAsync().await()
|
AoDParser.getSubscriptionInfoAsync().await()
|
||||||
|
|
|
@ -6,14 +6,13 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
import org.mosad.teapod.ui.activity.main.MainActivity
|
|
||||||
import org.mosad.teapod.databinding.FragmentHomeBinding
|
import org.mosad.teapod.databinding.FragmentHomeBinding
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
|
import org.mosad.teapod.ui.activity.main.MainActivity
|
||||||
import org.mosad.teapod.util.ItemMedia
|
import org.mosad.teapod.util.ItemMedia
|
||||||
import org.mosad.teapod.util.StorageController
|
import org.mosad.teapod.util.StorageController
|
||||||
import org.mosad.teapod.util.adapter.MediaItemAdapter
|
import org.mosad.teapod.util.adapter.MediaItemAdapter
|
||||||
|
@ -40,7 +39,7 @@ class HomeFragment : Fragment() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
lifecycleScope.launch {
|
||||||
context?.let {
|
context?.let {
|
||||||
initHighlight()
|
initHighlight()
|
||||||
initRecyclerViews()
|
initRecyclerViews()
|
||||||
|
@ -101,7 +100,7 @@ class HomeFragment : Fragment() {
|
||||||
private fun initActions() {
|
private fun initActions() {
|
||||||
binding.buttonPlayHighlight.setOnClickListener {
|
binding.buttonPlayHighlight.setOnClickListener {
|
||||||
// TODO get next episode
|
// TODO get next episode
|
||||||
GlobalScope.launch {
|
lifecycleScope.launch {
|
||||||
val media = AoDParser.getMediaById(highlightMedia.id)
|
val media = AoDParser.getMediaById(highlightMedia.id)
|
||||||
|
|
||||||
Log.d(javaClass.name, "Starting Player with mediaId: ${media.id}")
|
Log.d(javaClass.name, "Starting Player with mediaId: ${media.id}")
|
||||||
|
|
|
@ -5,10 +5,8 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import kotlinx.coroutines.Dispatchers
|
import androidx.lifecycle.lifecycleScope
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.mosad.teapod.databinding.FragmentLibraryBinding
|
import org.mosad.teapod.databinding.FragmentLibraryBinding
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
import org.mosad.teapod.util.adapter.MediaItemAdapter
|
import org.mosad.teapod.util.adapter.MediaItemAdapter
|
||||||
|
@ -29,18 +27,16 @@ class LibraryFragment : Fragment() {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
// init async
|
// init async
|
||||||
GlobalScope.launch {
|
lifecycleScope.launch {
|
||||||
// create and set the adapter, needs context
|
// create and set the adapter, needs context
|
||||||
withContext(Dispatchers.Main) {
|
context?.let {
|
||||||
context?.let {
|
adapter = MediaItemAdapter(AoDParser.itemMediaList)
|
||||||
adapter = MediaItemAdapter(AoDParser.itemMediaList)
|
adapter.onItemClick = { mediaId, _ ->
|
||||||
adapter.onItemClick = { mediaId, _ ->
|
activity?.showFragment(MediaFragment(mediaId))
|
||||||
activity?.showFragment(MediaFragment(mediaId))
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.recyclerMediaLibrary.adapter = adapter
|
|
||||||
binding.recyclerMediaLibrary.addItemDecoration(MediaItemDecoration(9))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.recyclerMediaLibrary.adapter = adapter
|
||||||
|
binding.recyclerMediaLibrary.addItemDecoration(MediaItemDecoration(9))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,20 +10,21 @@ import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import jp.wasabeef.glide.transformations.BlurTransformation
|
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.launch
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
import org.mosad.teapod.databinding.FragmentMediaBinding
|
import org.mosad.teapod.databinding.FragmentMediaBinding
|
||||||
import org.mosad.teapod.ui.activity.main.MainActivity
|
import org.mosad.teapod.ui.activity.main.MainActivity
|
||||||
import org.mosad.teapod.ui.activity.main.viewmodel.MediaFragmentViewModel
|
import org.mosad.teapod.ui.activity.main.viewmodel.MediaFragmentViewModel
|
||||||
import org.mosad.teapod.util.*
|
|
||||||
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.StorageController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The media detail fragment.
|
* The media detail fragment.
|
||||||
|
@ -61,13 +62,12 @@ class MediaFragment(private val mediaId: Int) : Fragment() {
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
|
||||||
|
lifecycleScope.launch {
|
||||||
model.load(mediaId) // load the streams and tmdb for the selected media
|
model.load(mediaId) // load the streams and tmdb for the selected media
|
||||||
|
|
||||||
if (this@MediaFragment.isAdded) {
|
updateGUI()
|
||||||
updateGUI()
|
initActions()
|
||||||
initActions()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.SearchView
|
import android.widget.SearchView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import kotlinx.coroutines.*
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.mosad.teapod.databinding.FragmentSearchBinding
|
import org.mosad.teapod.databinding.FragmentSearchBinding
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
import org.mosad.teapod.util.decoration.MediaItemDecoration
|
import org.mosad.teapod.util.decoration.MediaItemDecoration
|
||||||
|
@ -26,9 +27,8 @@ class SearchFragment : Fragment() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
GlobalScope.launch {
|
lifecycleScope.launch {
|
||||||
// create and set the adapter, needs context
|
// create and set the adapter, needs context
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
context?.let {
|
context?.let {
|
||||||
adapter = MediaItemAdapter(AoDParser.itemMediaList)
|
adapter = MediaItemAdapter(AoDParser.itemMediaList)
|
||||||
adapter!!.onItemClick = { mediaId, _ ->
|
adapter!!.onItemClick = { mediaId, _ ->
|
||||||
|
@ -39,7 +39,6 @@ class SearchFragment : Fragment() {
|
||||||
binding.recyclerMediaSearch.adapter = adapter
|
binding.recyclerMediaSearch.adapter = adapter
|
||||||
binding.recyclerMediaSearch.addItemDecoration(MediaItemDecoration(9))
|
binding.recyclerMediaSearch.addItemDecoration(MediaItemDecoration(9))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initActions()
|
initActions()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
import org.mosad.teapod.databinding.FragmentOnLoginBinding
|
import org.mosad.teapod.databinding.FragmentOnLoginBinding
|
||||||
|
@ -35,7 +36,7 @@ class OnLoginFragment: Fragment() {
|
||||||
EncryptedPreferences.saveCredentials(email, password, requireContext()) // save the credentials
|
EncryptedPreferences.saveCredentials(email, password, requireContext()) // save the credentials
|
||||||
|
|
||||||
binding.buttonLogin.isClickable = false
|
binding.buttonLogin.isClickable = false
|
||||||
loginJob = GlobalScope.launch {
|
loginJob = lifecycleScope.launch {
|
||||||
if (AoDParser.login()) {
|
if (AoDParser.login()) {
|
||||||
// if login was successful, switch to main
|
// if login was successful, switch to main
|
||||||
if (activity is OnboardingActivity) {
|
if (activity is OnboardingActivity) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import androidx.annotation.RequiresApi
|
||||||
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 androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
import com.google.android.exoplayer2.Player
|
import com.google.android.exoplayer2.Player
|
||||||
import com.google.android.exoplayer2.ui.StyledPlayerControlView
|
import com.google.android.exoplayer2.ui.StyledPlayerControlView
|
||||||
|
@ -26,7 +27,6 @@ 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.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
|
@ -255,7 +255,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
timerUpdates = Timer().scheduleAtFixedRate(0, 500) {
|
timerUpdates = Timer().scheduleAtFixedRate(0, 500) {
|
||||||
GlobalScope.launch {
|
lifecycleScope.launch {
|
||||||
var btnNextEpIsVisible: Boolean
|
var btnNextEpIsVisible: Boolean
|
||||||
var controlsVisible: Boolean
|
var controlsVisible: Boolean
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.app.Application
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||||
|
@ -11,6 +12,7 @@ import com.google.android.exoplayer2.source.MediaSource
|
||||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
|
||||||
import com.google.android.exoplayer2.util.Util
|
import com.google.android.exoplayer2.util.Util
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
import org.mosad.teapod.parser.AoDParser
|
import org.mosad.teapod.parser.AoDParser
|
||||||
|
@ -107,7 +109,9 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
|
|
||||||
// if episodes has not been watched, mark as watched
|
// if episodes has not been watched, mark as watched
|
||||||
if (!episode.watched) {
|
if (!episode.watched) {
|
||||||
AoDParser.markAsWatched(media.id, episode.id)
|
viewModelScope.launch {
|
||||||
|
AoDParser.markAsWatched(media.id, episode.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,16 +3,12 @@ package org.mosad.teapod.util
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.mosad.teapod.R
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
import java.io.FileWriter
|
import java.io.FileWriter
|
||||||
import java.lang.Exception
|
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This controller contains the logic for permanently saved data.
|
* This controller contains the logic for permanently saved data.
|
||||||
|
@ -45,7 +41,7 @@ object StorageController {
|
||||||
fun saveMyList(context: Context): Job {
|
fun saveMyList(context: Context): Job {
|
||||||
val file = File(context.filesDir, fileNameMyList)
|
val file = File(context.filesDir, fileNameMyList)
|
||||||
|
|
||||||
return GlobalScope.launch(Dispatchers.IO) {
|
return CoroutineScope(Dispatchers.IO).launch {
|
||||||
file.writeText(Gson().toJson(myList.distinct()))
|
file.writeText(Gson().toJson(myList.distinct()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ class TMDBApiController {
|
||||||
val searchTerm = title.replace("(Sub)", "").trim()
|
val searchTerm = title.replace("(Sub)", "").trim()
|
||||||
|
|
||||||
return when (type) {
|
return when (type) {
|
||||||
MediaType.MOVIE -> searchMovie(searchTerm).await()
|
MediaType.MOVIE -> searchMovie(searchTerm)
|
||||||
MediaType.TVSHOW -> searchTVShow(searchTerm).await()
|
MediaType.TVSHOW -> searchTVShow(searchTerm)
|
||||||
else -> {
|
else -> {
|
||||||
Log.e(javaClass.name, "Wrong Type: $type")
|
Log.e(javaClass.name, "Wrong Type: $type")
|
||||||
TMDBResponse()
|
TMDBResponse()
|
||||||
|
@ -34,62 +34,56 @@ class TMDBApiController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchTVShow(title: String): Deferred<TMDBResponse> {
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
private suspend fun searchTVShow(title: String): TMDBResponse = withContext(Dispatchers.IO) {
|
||||||
val url = URL("$searchTVUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
|
val url = URL("$searchTVUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
|
||||||
|
val response = JsonParser.parseString(url.readText()).asJsonObject
|
||||||
|
//println(response)
|
||||||
|
|
||||||
return GlobalScope.async {
|
return@withContext if (response.get("total_results").asInt > 0) {
|
||||||
val response = JsonParser.parseString(url.readText()).asJsonObject
|
response.get("results").asJsonArray.first().asJsonObject.let {
|
||||||
//println(response)
|
val id = getStringNotNull(it, "id").toInt()
|
||||||
|
val overview = getStringNotNull(it, "overview")
|
||||||
|
val posterPath = getStringNotNullPrefix(it, "poster_path", imageUrl)
|
||||||
|
val backdropPath = getStringNotNullPrefix(it, "backdrop_path", imageUrl)
|
||||||
|
|
||||||
if (response.get("total_results").asInt > 0) {
|
TMDBResponse(id, "", overview, posterPath, backdropPath)
|
||||||
response.get("results").asJsonArray.first().asJsonObject.let {
|
|
||||||
val id = getStringNotNull(it, "id").toInt()
|
|
||||||
val overview = getStringNotNull(it, "overview")
|
|
||||||
val posterPath = getStringNotNullPrefix(it, "poster_path", imageUrl)
|
|
||||||
val backdropPath = getStringNotNullPrefix(it, "backdrop_path", imageUrl)
|
|
||||||
|
|
||||||
TMDBResponse(id, "", overview, posterPath, backdropPath)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TMDBResponse()
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
TMDBResponse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchMovie(title: String): Deferred<TMDBResponse> {
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
private suspend fun searchMovie(title: String): TMDBResponse = withContext(Dispatchers.IO) {
|
||||||
val url = URL("$searchMovieUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
|
val url = URL("$searchMovieUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
|
||||||
|
val response = JsonParser.parseString(url.readText()).asJsonObject
|
||||||
|
//println(response)
|
||||||
|
|
||||||
return GlobalScope.async {
|
return@withContext if (response.get("total_results").asInt > 0) {
|
||||||
val response = JsonParser.parseString(url.readText()).asJsonObject
|
response.get("results").asJsonArray.first().asJsonObject.let {
|
||||||
//println(response)
|
val id = getStringNotNull(it,"id").toInt()
|
||||||
|
val overview = getStringNotNull(it,"overview")
|
||||||
|
val posterPath = getStringNotNullPrefix(it, "poster_path", imageUrl)
|
||||||
|
val backdropPath = getStringNotNullPrefix(it, "backdrop_path", imageUrl)
|
||||||
|
val runtime = getMovieRuntime(id)
|
||||||
|
|
||||||
if (response.get("total_results").asInt > 0) {
|
TMDBResponse(id, "", overview, posterPath, backdropPath, runtime)
|
||||||
response.get("results").asJsonArray.first().asJsonObject.let {
|
|
||||||
val id = getStringNotNull(it,"id").toInt()
|
|
||||||
val overview = getStringNotNull(it,"overview")
|
|
||||||
val posterPath = getStringNotNullPrefix(it, "poster_path", imageUrl)
|
|
||||||
val backdropPath = getStringNotNullPrefix(it, "backdrop_path", imageUrl)
|
|
||||||
val runtime = getMovieRuntime(id)
|
|
||||||
|
|
||||||
TMDBResponse(id, "", overview, posterPath, backdropPath, runtime)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TMDBResponse()
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
TMDBResponse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* currently only used for runtime, need a rework
|
* currently only used for runtime, need a rework
|
||||||
*/
|
*/
|
||||||
fun getMovieRuntime(id: Int): Int = runBlocking {
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
suspend fun getMovieRuntime(id: Int): Int = withContext(Dispatchers.IO) {
|
||||||
val url = URL("$getMovieUrl/$id?api_key=$apiKey&language=$language")
|
val url = URL("$getMovieUrl/$id?api_key=$apiKey&language=$language")
|
||||||
|
|
||||||
GlobalScope.async {
|
val response = JsonParser.parseString(url.readText()).asJsonObject
|
||||||
val response = JsonParser.parseString(url.readText()).asJsonObject
|
return@withContext getStringNotNull(response,"runtime").toInt()
|
||||||
|
|
||||||
return@async getStringNotNull(response,"runtime").toInt()
|
|
||||||
}.await()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = "1.5.0"
|
ext.kotlin_version = "1.5.10"
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
Loading…
Reference in New Issue