add metadb support for crunchyroll
also remove gson snice it's unused now
This commit is contained in:
parent
7d32cecd89
commit
7ca2d17a05
|
@ -60,7 +60,6 @@ dependencies {
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
|
||||||
|
|
||||||
implementation 'com.google.android.material:material:1.5.0'
|
implementation 'com.google.android.material:material:1.5.0'
|
||||||
implementation 'com.google.code.gson:gson:2.8.8' // TODO remove, still used by metadb
|
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.15.0'
|
implementation 'com.google.android.exoplayer:exoplayer-core:2.15.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-hls:2.15.0'
|
implementation 'com.google.android.exoplayer:exoplayer-hls:2.15.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-dash:2.15.0'
|
implementation 'com.google.android.exoplayer:exoplayer-dash:2.15.0'
|
||||||
|
|
|
@ -24,10 +24,6 @@
|
||||||
|
|
||||||
-keep class org.json.** { *; }
|
-keep class org.json.** { *; }
|
||||||
|
|
||||||
#Gson
|
|
||||||
-keepattributes Signature
|
|
||||||
-dontwarn sun.misc.**
|
|
||||||
|
|
||||||
# kotlinx.serialization
|
# kotlinx.serialization
|
||||||
# Keep `Companion` object fields of serializable classes.
|
# Keep `Companion` object fields of serializable classes.
|
||||||
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
|
||||||
|
|
|
@ -45,6 +45,7 @@ import org.mosad.teapod.ui.activity.onboarding.OnboardingActivity
|
||||||
import org.mosad.teapod.ui.activity.player.PlayerActivity
|
import org.mosad.teapod.ui.activity.player.PlayerActivity
|
||||||
import org.mosad.teapod.ui.components.LoginDialog
|
import org.mosad.teapod.ui.components.LoginDialog
|
||||||
import org.mosad.teapod.util.DataTypes
|
import org.mosad.teapod.util.DataTypes
|
||||||
|
import org.mosad.teapod.util.metadb.MetaDBController
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
|
@ -141,6 +142,9 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
|
||||||
Preferences.load(this)
|
Preferences.load(this)
|
||||||
EncryptedPreferences.readCredentials(this)
|
EncryptedPreferences.readCredentials(this)
|
||||||
|
|
||||||
|
// load meta db at the start, it doesn't depend on any third party
|
||||||
|
val metaJob = initMetaDB()
|
||||||
|
|
||||||
// always initialize the api token
|
// always initialize the api token
|
||||||
Crunchyroll.initBasicApiToken()
|
Crunchyroll.initBasicApiToken()
|
||||||
|
|
||||||
|
@ -152,14 +156,17 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
|
||||||
) {
|
) {
|
||||||
showOnboarding()
|
showOnboarding()
|
||||||
} else {
|
} else {
|
||||||
runBlocking { initCrunchyroll().joinAll() }
|
runBlocking {
|
||||||
|
initCrunchyroll().joinAll()
|
||||||
|
metaJob.join() // meta loading should be done here
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.i(classTag, "loading in $time ms")
|
Log.i(classTag, "loading in $time ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initCrunchyroll(): List<Job> {
|
private fun initCrunchyroll(): List<Job> {
|
||||||
val scope = CoroutineScope(Dispatchers.IO + CoroutineName("InitialLoadingScope"))
|
val scope = CoroutineScope(Dispatchers.IO + CoroutineName("InitialCrunchyLoading"))
|
||||||
return listOf(
|
return listOf(
|
||||||
scope.launch { Crunchyroll.index() },
|
scope.launch { Crunchyroll.index() },
|
||||||
scope.launch { Crunchyroll.account() },
|
scope.launch { Crunchyroll.account() },
|
||||||
|
@ -172,6 +179,11 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initMetaDB(): Job {
|
||||||
|
val scope = CoroutineScope(Dispatchers.IO + CoroutineName("InitialMetaDBLoading"))
|
||||||
|
return scope.launch { MetaDBController.list() }
|
||||||
|
}
|
||||||
|
|
||||||
private fun showLoginDialog() {
|
private fun showLoginDialog() {
|
||||||
LoginDialog(this, false).positiveButton {
|
LoginDialog(this, false).positiveButton {
|
||||||
EncryptedPreferences.saveCredentials(login, password, context)
|
EncryptedPreferences.saveCredentials(login, password, context)
|
||||||
|
|
|
@ -107,8 +107,6 @@ class AboutFragment : Fragment() {
|
||||||
"https://github.com/material-components/material-components-android", License.APACHE2),
|
"https://github.com/material-components/material-components-android", License.APACHE2),
|
||||||
ThirdPartyComponent("ExoPlayer", "2014 - 2020", "The Android Open Source Project",
|
ThirdPartyComponent("ExoPlayer", "2014 - 2020", "The Android Open Source Project",
|
||||||
"https://github.com/google/ExoPlayer", License.APACHE2),
|
"https://github.com/google/ExoPlayer", License.APACHE2),
|
||||||
ThirdPartyComponent("Gson", "2008", "Google Inc.",
|
|
||||||
"https://github.com/google/gson", License.APACHE2),
|
|
||||||
ThirdPartyComponent("Material design icons", "2020", "Google Inc.",
|
ThirdPartyComponent("Material design icons", "2020", "Google Inc.",
|
||||||
"https://github.com/google/material-design-icons", License.APACHE2),
|
"https://github.com/google/material-design-icons", License.APACHE2),
|
||||||
ThirdPartyComponent("Material Dialogs", "", "Aidan Follestad",
|
ThirdPartyComponent("Material Dialogs", "", "Aidan Follestad",
|
||||||
|
|
|
@ -8,7 +8,7 @@ import kotlinx.coroutines.launch
|
||||||
import org.mosad.teapod.parser.crunchyroll.*
|
import org.mosad.teapod.parser.crunchyroll.*
|
||||||
import org.mosad.teapod.preferences.Preferences
|
import org.mosad.teapod.preferences.Preferences
|
||||||
import org.mosad.teapod.util.DataTypes.MediaType
|
import org.mosad.teapod.util.DataTypes.MediaType
|
||||||
import org.mosad.teapod.util.Meta
|
import org.mosad.teapod.util.metadb.Meta
|
||||||
import org.mosad.teapod.util.tmdb.*
|
import org.mosad.teapod.util.tmdb.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,7 +59,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
|
||||||
).joinAll()
|
).joinAll()
|
||||||
// println("series: $seriesCrunchy")
|
// println("series: $seriesCrunchy")
|
||||||
// println("seasons: $seasonsCrunchy")
|
// println("seasons: $seasonsCrunchy")
|
||||||
println(upNextSeries)
|
// println(upNextSeries)
|
||||||
|
|
||||||
// load the preferred season (preferred language, language per season, not per stream)
|
// load the preferred season (preferred language, language per season, not per stream)
|
||||||
currentSeasonCrunchy = seasonsCrunchy.getPreferredSeason(Preferences.preferredLocale)
|
currentSeasonCrunchy = seasonsCrunchy.getPreferredSeason(Preferences.preferredLocale)
|
||||||
|
|
|
@ -425,7 +425,6 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
val seekTime = (it.openingStart + it.openingDuration) - model.player.currentPosition
|
val seekTime = (it.openingStart + it.openingDuration) - model.player.currentPosition
|
||||||
model.seekToOffset(seekTime)
|
model.seekToOffset(seekTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -46,8 +46,10 @@ import org.mosad.teapod.parser.crunchyroll.NoneEpisode
|
||||||
import org.mosad.teapod.parser.crunchyroll.NoneEpisodes
|
import org.mosad.teapod.parser.crunchyroll.NoneEpisodes
|
||||||
import org.mosad.teapod.parser.crunchyroll.NonePlayback
|
import org.mosad.teapod.parser.crunchyroll.NonePlayback
|
||||||
import org.mosad.teapod.preferences.Preferences
|
import org.mosad.teapod.preferences.Preferences
|
||||||
import org.mosad.teapod.util.EpisodeMeta
|
import org.mosad.teapod.util.metadb.EpisodeMeta
|
||||||
import org.mosad.teapod.util.tmdb.TMDBTVSeason
|
import org.mosad.teapod.util.metadb.Meta
|
||||||
|
import org.mosad.teapod.util.metadb.MetaDBController
|
||||||
|
import org.mosad.teapod.util.metadb.TVShowMeta
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,6 +58,7 @@ import java.util.*
|
||||||
* the next episode will be update and the callback is handled.
|
* the next episode will be update and the callback is handled.
|
||||||
*/
|
*/
|
||||||
class PlayerViewModel(application: Application) : AndroidViewModel(application) {
|
class PlayerViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
private val classTag = javaClass.name
|
||||||
|
|
||||||
val player = SimpleExoPlayer.Builder(application).build()
|
val player = SimpleExoPlayer.Builder(application).build()
|
||||||
private val dataSourceFactory = DefaultDataSourceFactory(application, Util.getUserAgent(application, "Teapod"))
|
private val dataSourceFactory = DefaultDataSourceFactory(application, Util.getUserAgent(application, "Teapod"))
|
||||||
|
@ -65,13 +68,12 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
private var currentPlayhead: Long = 0
|
private var currentPlayhead: Long = 0
|
||||||
|
|
||||||
// tmdb/meta data
|
// tmdb/meta data
|
||||||
// TODO meta data currently not implemented for cr
|
var mediaMeta: Meta? = null
|
||||||
// var mediaMeta: Meta? = null
|
|
||||||
// internal set
|
|
||||||
var tmdbTVSeason: TMDBTVSeason? =null
|
|
||||||
internal set
|
internal set
|
||||||
var currentEpisodeMeta: EpisodeMeta? = null
|
var currentEpisodeMeta: EpisodeMeta? = null
|
||||||
internal set
|
internal set
|
||||||
|
// var tmdbTVSeason: TMDBTVSeason? =null
|
||||||
|
// internal set
|
||||||
|
|
||||||
// crunchyroll episodes/playback
|
// crunchyroll episodes/playback
|
||||||
var episodes = NoneEpisodes
|
var episodes = NoneEpisodes
|
||||||
|
@ -108,7 +110,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
mediaSession.release()
|
mediaSession.release()
|
||||||
player.release()
|
player.release()
|
||||||
|
|
||||||
Log.d(javaClass.name, "Released player")
|
Log.d(classTag, "Released player")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,22 +126,12 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
|
|
||||||
fun loadMediaAsync(seasonId: String, episodeId: String) = viewModelScope.launch {
|
fun loadMediaAsync(seasonId: String, episodeId: String) = viewModelScope.launch {
|
||||||
episodes = Crunchyroll.episodes(seasonId)
|
episodes = Crunchyroll.episodes(seasonId)
|
||||||
|
mediaMeta = loadMediaMeta(episodes.items.first().seriesId)
|
||||||
|
|
||||||
|
Log.d(classTag, "meta: $mediaMeta")
|
||||||
|
|
||||||
setCurrentEpisode(episodeId)
|
setCurrentEpisode(episodeId)
|
||||||
playCurrentMedia(currentPlayhead)
|
playCurrentMedia(currentPlayhead)
|
||||||
|
|
||||||
// TODO reimplement for cr
|
|
||||||
// run async as it should be loaded by the time the episodes a
|
|
||||||
// viewModelScope.launch {
|
|
||||||
// // get tmdb season info, if metaDB knows the tv show
|
|
||||||
// if (media.type == DataTypes.MediaType.TVSHOW && mediaMeta != null) {
|
|
||||||
// val tvShowMeta = mediaMeta as TVShowMeta
|
|
||||||
// tmdbTVSeason = TMDBApiController().getTVSeasonDetails(tvShowMeta.tmdbId, tvShowMeta.tmdbSeasonNumber)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// currentEpisodeMeta = getEpisodeMetaByAoDMediaId(currentEpisodeAoD.mediaId)
|
|
||||||
// currentLanguage = currentEpisodeAoD.getPreferredStream(preferredLanguage).language
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setLanguage(language: Locale) {
|
fun setLanguage(language: Locale) {
|
||||||
|
@ -174,6 +166,15 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
episode.id == episodeId
|
episode.id == episodeId
|
||||||
} ?: NoneEpisode
|
} ?: NoneEpisode
|
||||||
|
|
||||||
|
// update current episode meta
|
||||||
|
currentEpisodeMeta = if (mediaMeta is TVShowMeta && currentEpisode.episodeNumber != null) {
|
||||||
|
(mediaMeta as TVShowMeta)
|
||||||
|
.seasons[currentEpisode.seasonNumber - 1]
|
||||||
|
.episodes[currentEpisode.episodeNumber!! - 1]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
// update player gui (title, next ep button) after currentEpisode has changed
|
// update player gui (title, next ep button) after currentEpisode has changed
|
||||||
currentEpisodeChangedListener.forEach { it() }
|
currentEpisodeChangedListener.forEach { it() }
|
||||||
|
|
||||||
|
@ -195,9 +196,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
println("loaded playback ${currentEpisode.playback}")
|
Log.i(classTag, "playback: ${currentEpisode.playback}")
|
||||||
|
|
||||||
// TODO update metadata and language (it should not be needed to update the language here!)
|
|
||||||
|
|
||||||
if (startPlayback) {
|
if (startPlayback) {
|
||||||
playCurrentMedia()
|
playCurrentMedia()
|
||||||
|
@ -227,7 +226,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
currentPlayback.streams.adaptive_hls.entries.first().value.url
|
currentPlayback.streams.adaptive_hls.entries.first().value.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println("stream url: $url")
|
Log.d(classTag, "stream url: $url")
|
||||||
|
|
||||||
// create the media source object
|
// create the media source object
|
||||||
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
|
val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(
|
||||||
|
@ -266,25 +265,9 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||||
return episodes.items.lastOrNull()?.id == currentEpisode.id
|
return episodes.items.lastOrNull()?.id == currentEpisode.id
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO reimplement for cr
|
private suspend fun loadMediaMeta(crSeriesId: String): Meta? {
|
||||||
// fun getEpisodeMetaByAoDMediaId(aodMediaId: Int): EpisodeMeta? {
|
return MetaDBController.getTVShowMetadata(crSeriesId)
|
||||||
// val meta = mediaMeta
|
}
|
||||||
// return if (meta is TVShowMeta) {
|
|
||||||
// meta.episodes.firstOrNull { it.aodMediaId == aodMediaId }
|
|
||||||
// } else {
|
|
||||||
// null
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private suspend fun loadMediaMeta(aodId: Int): Meta? {
|
|
||||||
// return if (media.type == DataTypes.MediaType.TVSHOW) {
|
|
||||||
// MetaDBController().getTVShowMetadata(aodId)
|
|
||||||
// } else {
|
|
||||||
// null
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return null
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the playhead of the current episode, if currentPosition > 1000ms.
|
* Update the playhead of the current episode, if currentPosition > 1000ms.
|
||||||
|
|
|
@ -28,7 +28,7 @@ class EpisodesListPlayer @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
model?.let {
|
model?.let {
|
||||||
adapterRecEpisodes = PlayerEpisodeItemAdapter(model.episodes, model.tmdbTVSeason?.episodes)
|
adapterRecEpisodes = PlayerEpisodeItemAdapter(model.episodes)
|
||||||
adapterRecEpisodes.onImageClick = {_, episodeId ->
|
adapterRecEpisodes.onImageClick = {_, episodeId ->
|
||||||
(this.parent as ViewGroup).removeView(this)
|
(this.parent as ViewGroup).removeView(this)
|
||||||
model.setCurrentEpisode(episodeId, startPlayback = true)
|
model.setCurrentEpisode(episodeId, startPlayback = true)
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
/**
|
|
||||||
* Teapod
|
|
||||||
*
|
|
||||||
* Copyright 2020-2022 <seil0@mosad.xyz>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
||||||
* MA 02110-1301, USA.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.mosad.teapod.util
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO remove gson usage
|
|
||||||
*/
|
|
||||||
class MetaDBController {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val repoUrl = "https://gitlab.com/Seil0/teapodmetadb/-/raw/main/aod/"
|
|
||||||
|
|
||||||
var mediaList = MediaList(listOf())
|
|
||||||
private var metaCacheList = arrayListOf<Meta>()
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
suspend fun list() = withContext(Dispatchers.IO) {
|
|
||||||
val url = URL("$repoUrl/list.json")
|
|
||||||
val json = url.readText()
|
|
||||||
|
|
||||||
mediaList = Gson().fromJson(json, MediaList::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the meta data for a movie from MetaDB
|
|
||||||
* @param aodId The AoD id of the media
|
|
||||||
* @return A meta movie object, or null if not found
|
|
||||||
*/
|
|
||||||
suspend fun getMovieMetadata(aodId: Int): MovieMeta? {
|
|
||||||
return metaCacheList.firstOrNull {
|
|
||||||
it.aodId == aodId
|
|
||||||
} as MovieMeta? ?: getMovieMetadataFromDB(aodId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the meta data for a tv show from MetaDB
|
|
||||||
* @param aodId The AoD id of the media
|
|
||||||
* @return A meta tv show object, or null if not found
|
|
||||||
*/
|
|
||||||
suspend fun getTVShowMetadata(aodId: Int): TVShowMeta? {
|
|
||||||
return metaCacheList.firstOrNull {
|
|
||||||
it.aodId == aodId
|
|
||||||
} as TVShowMeta? ?: getTVShowMetadataFromDB(aodId)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
private suspend fun getMovieMetadataFromDB(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) {
|
|
||||||
Log.w(javaClass.name, "Waring: The requested file was not found. Requested ID: $aodId", ex)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
private suspend fun getTVShowMetadataFromDB(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) {
|
|
||||||
Log.w(javaClass.name, "Waring: The requested file was not found. Requested ID: $aodId", ex)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// class representing the media list json object
|
|
||||||
data class MediaList(
|
|
||||||
val media: List<Int>
|
|
||||||
)
|
|
||||||
|
|
||||||
// abstract class used for meta data objects (tv, movie)
|
|
||||||
abstract class Meta {
|
|
||||||
abstract val id: Int
|
|
||||||
abstract val aodId: Int
|
|
||||||
abstract val tmdbId: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
// class representing the movie json object
|
|
||||||
data class MovieMeta(
|
|
||||||
override val id: Int,
|
|
||||||
@SerializedName("aod_id")
|
|
||||||
override val aodId: Int,
|
|
||||||
@SerializedName("tmdb_id")
|
|
||||||
override val tmdbId: Int
|
|
||||||
): Meta()
|
|
||||||
|
|
||||||
// class representing the tv show json object
|
|
||||||
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<EpisodeMeta>
|
|
||||||
): Meta()
|
|
||||||
|
|
||||||
// class used in TVShowMeta, part of the tv show json object
|
|
||||||
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: Long,
|
|
||||||
@SerializedName("opening_duration")
|
|
||||||
val openingDuration: Long,
|
|
||||||
@SerializedName("ending_start")
|
|
||||||
val endingStart: Long,
|
|
||||||
@SerializedName("ending_duration")
|
|
||||||
val endingDuration: Long
|
|
||||||
)
|
|
|
@ -12,7 +12,7 @@ import org.mosad.teapod.databinding.ItemEpisodePlayerBinding
|
||||||
import org.mosad.teapod.parser.crunchyroll.Episodes
|
import org.mosad.teapod.parser.crunchyroll.Episodes
|
||||||
import org.mosad.teapod.util.tmdb.TMDBTVEpisode
|
import org.mosad.teapod.util.tmdb.TMDBTVEpisode
|
||||||
|
|
||||||
class PlayerEpisodeItemAdapter(private val episodes: Episodes, private val tmdbEpisodes: List<TMDBTVEpisode>?) : RecyclerView.Adapter<PlayerEpisodeItemAdapter.EpisodeViewHolder>() {
|
class PlayerEpisodeItemAdapter(private val episodes: Episodes) : RecyclerView.Adapter<PlayerEpisodeItemAdapter.EpisodeViewHolder>() {
|
||||||
|
|
||||||
var onImageClick: ((seasonId: String, episodeId: String) -> Unit)? = null
|
var onImageClick: ((seasonId: String, episodeId: String) -> Unit)? = null
|
||||||
var currentSelected: Int = -1 // -1, since position should never be < 0
|
var currentSelected: Int = -1 // -1, since position should never be < 0
|
||||||
|
@ -39,8 +39,6 @@ class PlayerEpisodeItemAdapter(private val episodes: Episodes, private val tmdbE
|
||||||
holder.binding.textEpisodeTitle2.text = titleText
|
holder.binding.textEpisodeTitle2.text = titleText
|
||||||
holder.binding.textEpisodeDesc2.text = if (ep.description.isNotEmpty()) {
|
holder.binding.textEpisodeDesc2.text = if (ep.description.isNotEmpty()) {
|
||||||
ep.description
|
ep.description
|
||||||
} else if (tmdbEpisodes != null && position < tmdbEpisodes.size){
|
|
||||||
tmdbEpisodes[position].overview
|
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.mosad.teapod.util.metadb
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
// class representing the media list json object
|
||||||
|
@Serializable
|
||||||
|
data class MediaList(
|
||||||
|
@SerialName("media") val media: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
// abstract class used for meta data objects (tv, movie)
|
||||||
|
abstract class Meta {
|
||||||
|
abstract val id: Int
|
||||||
|
abstract val tmdbId: Int
|
||||||
|
abstract val crSeriesId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
// class representing the movie json object
|
||||||
|
@Serializable
|
||||||
|
data class MovieMeta(
|
||||||
|
@SerialName("id") override val id: Int,
|
||||||
|
@SerialName("tmdb_id") override val tmdbId: Int,
|
||||||
|
@SerialName("cr_series_id") override val crSeriesId: String,
|
||||||
|
): Meta()
|
||||||
|
|
||||||
|
// class representing the tv show json object
|
||||||
|
@Serializable
|
||||||
|
data class TVShowMeta(
|
||||||
|
@SerialName("id") override val id: Int,
|
||||||
|
@SerialName("tmdb_id") override val tmdbId: Int,
|
||||||
|
@SerialName("cr_series_id") override val crSeriesId: String,
|
||||||
|
@SerialName("seasons") val seasons: List<SeasonMeta>,
|
||||||
|
): Meta()
|
||||||
|
|
||||||
|
// class used in TVShowMeta, part of the tv show json object
|
||||||
|
@Serializable
|
||||||
|
data class SeasonMeta(
|
||||||
|
@SerialName("id") val id: Int,
|
||||||
|
@SerialName("tmdb_season_id") val tmdbSeasonId: Int,
|
||||||
|
@SerialName("tmdb_season_number") val tmdbSeasonNumber: Int,
|
||||||
|
@SerialName("cr_season_ids") val crSeasonIds: List<String>,
|
||||||
|
@SerialName("episodes") val episodes: List<EpisodeMeta>,
|
||||||
|
)
|
||||||
|
|
||||||
|
// class used in TVShowMeta, part of the tv show json object
|
||||||
|
@Serializable
|
||||||
|
data class EpisodeMeta(
|
||||||
|
@SerialName("id") val id: Int,
|
||||||
|
@SerialName("tmdb_episode_id") val tmdbEpisodeId: Int,
|
||||||
|
@SerialName("tmdb_episode_number") val tmdbEpisodeNumber: Int,
|
||||||
|
@SerialName("cr_episode_ids") val crEpisodeIds: List<String>,
|
||||||
|
@SerialName("opening_start") val openingStart: Long,
|
||||||
|
@SerialName("opening_duration") val openingDuration: Long,
|
||||||
|
@SerialName("ending_start") val endingStart: Long,
|
||||||
|
@SerialName("ending_duration") val endingDuration: Long
|
||||||
|
)
|
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* Teapod
|
||||||
|
*
|
||||||
|
* Copyright 2020-2022 <seil0@mosad.xyz>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.mosad.teapod.util.metadb
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.features.*
|
||||||
|
import io.ktor.client.features.json.*
|
||||||
|
import io.ktor.client.features.json.serializer.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
object MetaDBController {
|
||||||
|
private val TAG = javaClass.name
|
||||||
|
|
||||||
|
private const val repoUrl = "https://gitlab.com/Seil0/teapodmetadb/-/raw/main/crunchy/"
|
||||||
|
|
||||||
|
private val client = HttpClient {
|
||||||
|
install(JsonFeature) {
|
||||||
|
serializer = KotlinxSerializer(Json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mediaList = MediaList(listOf())
|
||||||
|
private var metaCacheList = arrayListOf<Meta>()
|
||||||
|
|
||||||
|
suspend fun list() = withContext(Dispatchers.IO) {
|
||||||
|
val raw: String = client.get("$repoUrl/list.json")
|
||||||
|
mediaList = Json.decodeFromString(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the meta data for a movie from MetaDB
|
||||||
|
* @param crSeriesId The crunchyroll media id
|
||||||
|
* @return A meta object, or null if not found
|
||||||
|
*/
|
||||||
|
suspend fun getTVShowMetadata(crSeriesId: String): TVShowMeta? {
|
||||||
|
return if (mediaList.media.contains(crSeriesId)) {
|
||||||
|
metaCacheList.firstOrNull {
|
||||||
|
it.crSeriesId == crSeriesId
|
||||||
|
} as TVShowMeta? ?: getTVShowMetadataFromDB(crSeriesId)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getTVShowMetadataFromDB(crSeriesId: String): TVShowMeta? = withContext(Dispatchers.IO) {
|
||||||
|
return@withContext try {
|
||||||
|
val raw: String = client.get("$repoUrl/tv/$crSeriesId/media.json")
|
||||||
|
val meta: TVShowMeta = Json.decodeFromString(raw)
|
||||||
|
metaCacheList.add(meta)
|
||||||
|
|
||||||
|
meta
|
||||||
|
} catch (ex: ClientRequestException) {
|
||||||
|
when (ex.response.status) {
|
||||||
|
HttpStatusCode.NotFound -> Log.w(TAG, "The requested file was not found. Series ID: $crSeriesId", ex)
|
||||||
|
else -> Log.e(TAG, "Error while requesting meta data. Series ID: $crSeriesId", ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
null // todo return none object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue