diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt
index b6e1502..69905a5 100644
--- a/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt
+++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt
@@ -46,6 +46,7 @@ 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.MetaDBController
import org.mosad.teapod.util.StorageController
import org.mosad.teapod.util.exitAndRemoveTask
import java.net.SocketTimeoutException
@@ -137,8 +138,12 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
*/
private fun load() {
val time = measureTimeMillis {
+ // start the initial loading
val loadingJob = CoroutineScope(Dispatchers.IO + CoroutineName("InitialLoadingScope"))
- .async { AoDParser.initialLoading() } // start the initial loading
+ .async {
+ launch { AoDParser.initialLoading() }
+ launch { MetaDBController.list() }
+ }
// load all saved stuff here
Preferences.load(this)
diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt
index 3a0010f..3d9e13e 100644
--- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt
+++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt
@@ -64,7 +64,6 @@ class MediaFragment(private val mediaId: Int) : Fragment() {
}
}.attach()
-
lifecycleScope.launch {
model.load(mediaId) // load the streams and tmdb for the selected media
diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt
index 78f480f..2a8b0fa 100644
--- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt
+++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt
@@ -28,7 +28,7 @@ class MediaFragmentEpisodes : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- adapterRecEpisodes = EpisodeItemAdapter(model.media.episodes)
+ adapterRecEpisodes = EpisodeItemAdapter(model.media.episodes, model.tmdbTVSeason?.episodes)
binding.recyclerEpisodes.adapter = adapterRecEpisodes
// set onItemClick only in adapter is initialized
diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt
index d155f31..e897f68 100644
--- a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt
+++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt
@@ -9,9 +9,11 @@ import org.mosad.teapod.util.DataTypes.MediaType
import org.mosad.teapod.util.tmdb.Movie
import org.mosad.teapod.util.tmdb.TMDBApiController
import org.mosad.teapod.util.tmdb.TMDBResult
+import org.mosad.teapod.util.tmdb.TVSeason
/**
* handle media, next ep and tmdb
+ * TODO this lives in activity, is this correct?
*/
class MediaFragmentViewModel(application: Application) : AndroidViewModel(application) {
@@ -21,16 +23,35 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
internal set
lateinit var tmdbResult: TMDBResult // TODO rename
internal set
+ var tmdbTVSeason: TVSeason? =null
+ internal set
+ var mediaMeta: Meta? = null
+ internal set
/**
* set media, tmdb and nextEpisode
+ * TODO run aod and tmdb load parallel
*/
suspend fun load(mediaId: Int) {
+ val tmdbApiController = TMDBApiController()
media = AoDParser.getMediaById(mediaId)
- val tmdbApiController = TMDBApiController()
- val searchTitle = stripTitleInfo(media.info.title)
- val tmdbId = tmdbApiController.search(searchTitle, media.type)
+ // check if metaDB knows the title
+ val tmdbId: Int = if (MetaDBController.mediaList.media.contains(media.id)) {
+ // load media info from metaDB
+ val metaDB = MetaDBController()
+ mediaMeta = when (media.type) {
+ MediaType.MOVIE -> metaDB.getMovieMetadata(media.id)
+ MediaType.TVSHOW -> metaDB.getTVShowMetadata(media.id)
+ else -> null
+ }
+
+ mediaMeta?.tmdbId ?: -1
+ } else {
+ // use tmdb search to get media info
+ mediaMeta = null // set mediaMeta to null, if metaDB doesn't know the media
+ tmdbApiController.search(stripTitleInfo(media.info.title), media.type)
+ }
tmdbResult = when (media.type) {
MediaType.MOVIE -> tmdbApiController.getMovieDetails(tmdbId)
@@ -39,16 +60,30 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
}
println(tmdbResult) // TODO
- // TESTING
- if (media.type == MediaType.TVSHOW) {
- val seasonNumber = guessSeasonFromTitle(media.info.title)
- Log.d("test", "season number: $seasonNumber")
-
- // TODO Important: only use tmdb info if media title and episode number match exactly
- val tmdbTVSeason = tmdbApiController.getTVSeasonDetails(tmdbId, seasonNumber)
- Log.d("test", "Season Info: $tmdbTVSeason.")
+ // get season info, if metaDB knows the tv show
+ tmdbTVSeason = if (media.type == MediaType.TVSHOW && mediaMeta != null) {
+ val tvShowMeta = mediaMeta as TVShowMeta
+ tmdbApiController.getTVSeasonDetails(tvShowMeta.tmdbId, tvShowMeta.tmdbSeasonNumber)
+ } else {
+ null
}
+ // TESTING
+// if (media.type == MediaType.TVSHOW) {
+// if (mediaMeta != null) {
+// val tvShowMeta = mediaMeta as TVShowMeta
+// val tmdbTVSeason = tmdbApiController.getTVSeasonDetails(tvShowMeta.tmdbId, tvShowMeta.tmdbSeasonNumber)
+// } else {
+// // for tv shows not in metaDB, try to guess/search
+//
+// val seasonNumber = guessSeasonFromTitle(media.info.title)
+// Log.d("test", "season number: $seasonNumber")
+//
+// val tmdbTVSeason = tmdbApiController.getTVSeasonDetails(tmdbId, seasonNumber)
+// Log.d("test", "Season Info: $tmdbTVSeason.")
+// }
+// }
+
// TESTING END
if (media.type == MediaType.TVSHOW) {
diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt b/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt
index 5dcd69f..c34d321 100644
--- a/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt
+++ b/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt
@@ -19,9 +19,7 @@ import kotlinx.coroutines.runBlocking
import org.mosad.teapod.R
import org.mosad.teapod.parser.AoDParser
import org.mosad.teapod.preferences.Preferences
-import org.mosad.teapod.util.DataTypes
-import org.mosad.teapod.util.Episode
-import org.mosad.teapod.util.Media
+import org.mosad.teapod.util.*
import java.util.*
import kotlin.collections.ArrayList
@@ -45,6 +43,8 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
internal set
var nextEpisode: Episode? = null
internal set
+ var mediaMeta: Meta? = null
+ internal set
var currentLanguage: Locale = Locale.ROOT
internal set
@@ -75,6 +75,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
fun loadMedia(mediaId: Int, episodeId: Int) {
runBlocking {
media = AoDParser.getMediaById(mediaId)
+ mediaMeta = loadMediaMeta(media.id)
}
currentEpisode = media.getEpisodeById(episodeId)
@@ -159,6 +160,14 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
}
}
+ private suspend fun loadMediaMeta(aodId: Int): Meta? {
+ return if (media.type == DataTypes.MediaType.TVSHOW) {
+ MetaDBController().getTVShowMetadata(aodId)
+ } else {
+ null
+ }
+ }
+
/**
* Based on the current episodeId, get the next episode. If there is no next
* episode, return null
diff --git a/app/src/main/java/org/mosad/teapod/util/MetaDBController.kt b/app/src/main/java/org/mosad/teapod/util/MetaDBController.kt
new file mode 100644
index 0000000..84564e0
--- /dev/null
+++ b/app/src/main/java/org/mosad/teapod/util/MetaDBController.kt
@@ -0,0 +1,119 @@
+package org.mosad.teapod.util
+
+import com.google.gson.Gson
+import com.google.gson.annotations.SerializedName
+import kotlinx.coroutines.*
+import java.io.FileNotFoundException
+import java.net.URL
+
+class MetaDBController {
+
+ companion object {
+ private const val repoUrl = "https://gitlab.com/Seil0/teapodmetadb/-/raw/main/aod/"
+
+ var mediaList = MediaList(listOf())
+ private var metaCacheList = arrayListOf()
+
+ @Suppress("BlockingMethodInNonBlockingContext")
+ suspend fun list() = withContext(Dispatchers.IO) {
+ val url = URL("$repoUrl/list.json")
+ val json = url.readText()
+
+ Thread.sleep(5000)
+
+ mediaList = Gson().fromJson(json, MediaList::class.java)
+ }
+ }
+
+ suspend fun getMovieMetadata(aodId: Int): MovieMeta? {
+ return metaCacheList.firstOrNull {
+ it.aodId == aodId
+ } as MovieMeta? ?: getMovieMetadata2(aodId)
+ }
+
+ suspend fun getTVShowMetadata(aodId: Int): TVShowMeta? {
+ return metaCacheList.firstOrNull {
+ it.aodId == aodId
+ } as TVShowMeta? ?: getTVShowMetadata2(aodId)
+ }
+
+ @Suppress("BlockingMethodInNonBlockingContext")
+ private suspend fun getMovieMetadata2(aodId: Int): MovieMeta? = withContext(Dispatchers.IO) {
+ val url = URL("$repoUrl/movie/$aodId/media.json")
+ return@withContext try {
+ val json = url.readText()
+ val meta = Gson().fromJson(json, MovieMeta::class.java)
+ metaCacheList.add(meta)
+
+ meta
+ } catch (ex: FileNotFoundException) {
+ null
+ }
+ }
+
+ @Suppress("BlockingMethodInNonBlockingContext")
+ private suspend fun getTVShowMetadata2(aodId: Int): TVShowMeta? = withContext(Dispatchers.IO) {
+ val url = URL("$repoUrl/tv/$aodId/media.json")
+ return@withContext try {
+ val json = url.readText()
+ val meta = Gson().fromJson(json, TVShowMeta::class.java)
+ metaCacheList.add(meta)
+
+ meta
+ } catch (ex: FileNotFoundException) {
+ null
+ }
+ }
+
+}
+
+// TODO move data classes
+data class MediaList(
+ val media: List
+)
+
+abstract class Meta {
+ abstract val id: Int
+ abstract val aodId: Int
+ abstract val tmdbId: Int
+}
+
+data class MovieMeta(
+ override val id: Int,
+ @SerializedName("aod_id")
+ override val aodId: Int,
+ @SerializedName("tmdb_id")
+ override val tmdbId: Int
+): Meta()
+
+data class TVShowMeta(
+ override val id: Int,
+ @SerializedName("aod_id")
+ override val aodId: Int,
+ @SerializedName("tmdb_id")
+ override val tmdbId: Int,
+ @SerializedName("tmdb_season_id")
+ val tmdbSeasonId: Int,
+ @SerializedName("tmdb_season_number")
+ val tmdbSeasonNumber: Int,
+ @SerializedName("episodes")
+ val episodes: List
+): Meta()
+
+data class EpisodeMeta(
+ val id: Int,
+ @SerializedName("aod_media_id")
+ val aodMediaId: Int,
+ @SerializedName("tmdb_id")
+ val tmdbId: Int,
+ @SerializedName("tmdb_number")
+ val tmdbNumber: Int,
+ @SerializedName("opening_start")
+ val openingStart: Int,
+ @SerializedName("opening_duration")
+ val openingDuration: Int,
+ @SerializedName("ending_start")
+ val endingStart: Int,
+ @SerializedName("ending_duration")
+ val endingDuration: Int
+)
diff --git a/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt b/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt
index 6eb467c..a131a3b 100644
--- a/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt
+++ b/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt
@@ -12,8 +12,9 @@ import jp.wasabeef.glide.transformations.RoundedCornersTransformation
import org.mosad.teapod.R
import org.mosad.teapod.databinding.ItemEpisodeBinding
import org.mosad.teapod.util.Episode
+import org.mosad.teapod.util.tmdb.TVEpisode
-class EpisodeItemAdapter(private val episodes: List) : RecyclerView.Adapter() {
+class EpisodeItemAdapter(private val episodes: List, private val tmdbEpisodes: List?) : RecyclerView.Adapter() {
var onImageClick: ((String, Int) -> Unit)? = null
@@ -32,7 +33,13 @@ class EpisodeItemAdapter(private val episodes: List) : RecyclerView.Ada
}
holder.binding.textEpisodeTitle.text = titleText
- holder.binding.textEpisodeDesc.text = ep.shortDesc
+ holder.binding.textEpisodeDesc.text = if (ep.shortDesc.isNotEmpty()) {
+ ep.shortDesc
+ } else if (tmdbEpisodes != null && position < tmdbEpisodes.size){
+ tmdbEpisodes[position].overview
+ } else {
+ ""
+ }
if (episodes[position].posterUrl.isNotEmpty()) {
Glide.with(context).load(ep.posterUrl)
diff --git a/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt b/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt
index e68250e..e5c4348 100644
--- a/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt
+++ b/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt
@@ -99,14 +99,14 @@ class TMDBApiController {
}
@Suppress("BlockingMethodInNonBlockingContext")
- suspend fun getTVSeasonDetails(tvId: Int, seasonNumber: Int): TVSeason = withContext(Dispatchers.IO) {
+ suspend fun getTVSeasonDetails(tvId: Int, seasonNumber: Int): TVSeason? = withContext(Dispatchers.IO) {
val url = URL("$detailsTVUrl/$tvId/season/$seasonNumber?api_key=$apiKey&language=$language")
val response = try {
JsonParser.parseString(url.readText()).asJsonObject
} catch (ex: FileNotFoundException) {
Log.w(javaClass.name, "The resource you requested could not be found")
- return@withContext TVSeason(-1)
+ return@withContext null
}
// println(response)
@@ -114,25 +114,25 @@ class TMDBApiController {
val episodes = response.get("episodes").asJsonArray.map {
TVEpisode(
id = it.asJsonObject.get("id").asInt,
- name = it.asJsonObject.get("name")?.asString,
- overview = it.asJsonObject.get("overview")?.asString,
- airDate = it.asJsonObject.get("air_date")?.asString,
- episodeNumber = it.asJsonObject.get("episode_number")?.asInt
+ name = it.asJsonObject.get("name")?.asString ?: "",
+ overview = it.asJsonObject.get("overview")?.asString ?: "",
+ airDate = it.asJsonObject.get("air_date")?.asString ?: "",
+ episodeNumber = it.asJsonObject.get("episode_number")?.asInt ?: -1
)
}
TVSeason(
id = response.get("id").asInt,
- name = response.asJsonObject.get("name")?.asString,
- overview = response.asJsonObject.get("overview")?.asString,
- posterPath = response.asJsonObject.get("poster_path")?.asString,
- airDate = response.asJsonObject.get("air_date")?.asString,
+ name = response.asJsonObject.get("name")?.asString ?: "",
+ overview = response.asJsonObject.get("overview")?.asString ?: "",
+ posterPath = response.asJsonObject.get("poster_path")?.asString ?: "",
+ airDate = response.asJsonObject.get("air_date")?.asString ?: "",
episodes = episodes,
- seasonNumber = response.get("season_number")?.asInt
+ seasonNumber = response.get("season_number")?.asInt ?: -1
)
} catch (ex: Exception) {
Log.w(javaClass.name, "Error", ex)
- TVSeason(-1)
+ null
}
}
diff --git a/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBDataTypes.kt b/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBDataTypes.kt
index 608831a..08fdc70 100644
--- a/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBDataTypes.kt
+++ b/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBDataTypes.kt
@@ -32,19 +32,19 @@ data class TVShow(
data class TVSeason(
val id: Int,
- val name: String? = null,
- val overview: String? = null,
- val posterPath: String? = null,
- val airDate: String? = null,
- val episodes: List? = null,
- val seasonNumber: Int? = null
+ val name: String,
+ val overview: String,
+ val posterPath: String,
+ val airDate: String,
+ val episodes: List,
+ val seasonNumber: Int
)
// TODO decide whether to use nullable or not
data class TVEpisode(
val id: Int,
- val name: String? = null,
- val overview: String? = null,
- val airDate: String? = null,
- val episodeNumber: Int? = null
+ val name: String,
+ val overview: String,
+ val airDate: String,
+ val episodeNumber: Int
)
\ No newline at end of file