diff --git a/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt b/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt index 95ea3a4..11627ba 100644 --- a/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt +++ b/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt @@ -31,7 +31,6 @@ import org.mosad.teapod.preferences.EncryptedPreferences import org.mosad.teapod.util.* import org.mosad.teapod.util.DataTypes.MediaType import java.io.IOException -import java.lang.NumberFormatException import java.util.* import kotlin.random.Random @@ -48,7 +47,6 @@ object AoDParser { private var csrfToken: String = "" private var loginSuccess = false - private val mediaList = arrayListOf() // actual media (data) TODO remove private val aodMediaList = arrayListOf() // actual media (data) // gui media @@ -109,29 +107,12 @@ object AoDParser { } } - /** - * get a media by it's ID (int) - * @return Media - */ - @Deprecated(message = "Use getMediaById2() instead") - suspend fun getMediaById(aodId: Int): Media { - val media = mediaList.first { it.id == aodId } - - if (media.episodes.isEmpty()) { - loadStreams(media).join() - - loadMediaAsync(media.id).await() - } - - return media - } - /** * get a media by it's ID (int) * @param aodId The AoD ID of the requested media * @return returns a AoDMedia of type Movie or TVShow if found, else return AoDMediaNone */ - suspend fun getMediaById2(aodId: Int): AoDMedia { + suspend fun getMediaById(aodId: Int): AoDMedia { return aodMediaList.firstOrNull { it.aodId == aodId } ?: try { loadMediaAsync(aodId).await().apply { @@ -141,8 +122,6 @@ object AoDParser { Log.e(javaClass.name, "Error while loading media $aodId", exn) AoDMediaNone } - - } /** @@ -165,12 +144,12 @@ object AoDParser { return baseUrl + subscriptionPath } - suspend fun markAsWatched(mediaId: Int, episodeId: Int) { - val episode = getMediaById(mediaId).getEpisodeById(episodeId) + suspend fun markAsWatched(aodId: Int, episodeId: Int) { + val episode = getMediaById(aodId).getEpisodeById(episodeId) episode.watched = true sendCallback(episode.watchedCallback) - Log.d(javaClass.name, "Marked episode ${episode.id} as watched") + Log.d(javaClass.name, "Marked episode ${episode.mediaId} as watched") } // TODO don't use jsoup here @@ -206,7 +185,6 @@ object AoDParser { //println(resAnimes) guiMediaList.clear() - mediaList.clear() val animes = resAnimes.select("div.animebox") guiMediaList.addAll( @@ -221,28 +199,7 @@ object AoDParser { } ) - // TODO legacy - resAnimes.select("div.animebox").forEach { - val id = it.select("p.animebox-link").select("a").attr("href") - .substringAfterLast("/").toInt() - val title = it.select("h3.animebox-title").text() - val image = it.select("p.animebox-image").select("img").attr("src") - val link = it.select("p.animebox-link").select("a").attr("href") - val type = when (it.select("p.animebox-link").select("a").text().lowercase(Locale.ROOT)) { - "zur serie" -> MediaType.TVSHOW - "zum film" -> MediaType.MOVIE - else -> MediaType.OTHER - } - val mediaShortText = it.select("p.animebox-shorttext").text() - - mediaList.add(Media(id, link, type).apply { - info.title = title - info.posterUrl = image - info.shortDesc = mediaShortText - }) - } - - Log.i(javaClass.name, "Total library size is: ${mediaList.size}") + Log.i(javaClass.name, "Total library size is: ${guiMediaList.size}") } } @@ -332,121 +289,17 @@ object AoDParser { } /** - * TODO rework the media loading process, don't modify media object * TODO catch SocketTimeoutException from loading to show a waring dialog - * load streams for the media path, movies have one episode - * @param media is used as call ba reference + * Load media async. Every media has a playlist. + * @param aodId The AoD ID of the requested media */ - private suspend fun loadStreams(media: Media) = coroutineScope { - launch(Dispatchers.IO) { - if (sessionCookies.isEmpty()) login() - - if (!loginSuccess) { - Log.w(javaClass.name, "Login, was not successful.") - 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 -> - aod.list.forEach { ep -> - try { - if (media.hasEpisode(ep.mediaid)) { - media.getEpisodeById(ep.mediaid).streams.add( - Stream(ep.sources.first().file, aod.language) - ) - } else { - media.episodes.add(Episode( - id = ep.mediaid, - streams = mutableListOf(Stream(ep.sources.first().file, aod.language)), - posterUrl = ep.image, - title = ep.title, - description = ep.description, - number = getNumberFromTitle(ep.title, media.type) - )) - } - } catch (ex: Exception) { - Log.w(javaClass.name, "Could not parse episode information.", ex) - } - } - } - 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") - } - } - private suspend fun loadMediaAsync(aodId: Int): Deferred = coroutineScope { return@coroutineScope async (Dispatchers.IO) { if (sessionCookies.isEmpty()) login() // TODO is this needed? // return none object, if login wasn't successful if (!loginSuccess) { - Log.w(javaClass.name, "Login, was not successful.") + Log.w(javaClass.name, "Login was not successful") return@async AoDMediaNone } @@ -461,7 +314,7 @@ object AoDParser { Log.d(javaClass.name, "New csrf token is $csrfToken") } - // playlist parsing TODO can this be async to the genral info marsing? + // playlist parsing TODO can this be async to the general info parsing? val besides = res.select("div.besides").first() val aodPlaylists = besides.select("input.streamstarter_html5").map { streamstarter -> parsePlaylistAsync( @@ -501,6 +354,7 @@ object AoDParser { if (mediaId != null) { ItemMedia(mediaId, mediaTitle, mediaImage) } else { + Log.i(javaClass.name, "MediaId for similar to $aodId was null") null } } @@ -522,6 +376,7 @@ object AoDParser { AoDEpisodeInfo(mediaId, episodeShortDesc, episodeWatched, episodeWatchedCallback) } else { + Log.i(javaClass.name, "Episode info for $aodId has empty streamstarter_html5 ") null } }.associateBy { it.aodMediaId } @@ -529,7 +384,7 @@ object AoDParser { mapOf() } - // TODO make AoDPlaylist to teapod playlist + // map the aod api playlist to a teapod playlist val playlist: List = aodPlaylists.awaitAll().flatMap { aodPlaylist -> aodPlaylist.list.mapIndexed { index, episode -> AoDEpisode( @@ -549,7 +404,6 @@ object AoDParser { it.streams.addAll(element.streams) } }.values.toList() - println("new playlist object: $playlist") return@async AoDMedia( aodId = aodId, @@ -615,22 +469,4 @@ object AoDParser { } } - /** - * get the episode number from the title - * @param title the episode title, containing a number after "Ep." - * @param type the media type, if not TVSHOW, return 0 - * @return the episode number, on NumberFormatException return 0 - */ - private fun getNumberFromTitle(title: String, type: MediaType): Int { - return if (type == MediaType.TVSHOW) { - try { - title.substringAfter(", Ep. ").toInt() - } catch (nex: NumberFormatException) { - 0 - } - } else { - 0 - } - } - } diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt index e9422f1..ae7ddd4 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt @@ -96,7 +96,7 @@ class HomeFragment : Fragment() { binding.buttonPlayHighlight.setOnClickListener { // TODO get next episode lifecycleScope.launch { - val media = AoDParser.getMediaById2(highlightMedia.id) + val media = AoDParser.getMediaById(highlightMedia.id) Log.d(javaClass.name, "Starting Player with mediaId: ${media.aodId}") (activity as MainActivity).startPlayer(media.aodId, media.playlist.first().mediaId) 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 2d8cef3..082a100 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 @@ -56,7 +56,7 @@ class MediaFragment(private val mediaId: Int) : Fragment() { binding.pagerEpisodesSimilar.offscreenPageLimit = 2 binding.pagerEpisodesSimilar.adapter = pagerAdapter TabLayoutMediator(binding.tabEpisodesSimilar, binding.pagerEpisodesSimilar) { tab, position -> - tab.text = if (model.media2.type == MediaType.TVSHOW && position == 0) { + tab.text = if (model.media.type == MediaType.TVSHOW && position == 0) { getString(R.string.episodes) } else { getString(R.string.similar_titles) @@ -75,9 +75,8 @@ class MediaFragment(private val mediaId: Int) : Fragment() { super.onResume() // update the next ep text if there is one, since it may have changed - println(model.nextEpisodeId) - if (model.media2.getEpisodeById(model.nextEpisodeId).title.isNotEmpty()) { - binding.textTitle.text = model.media2.getEpisodeById(model.nextEpisodeId).title + if (model.media.getEpisodeById(model.nextEpisodeId).title.isNotEmpty()) { + binding.textTitle.text = model.media.getEpisodeById(model.nextEpisodeId).title } } @@ -87,9 +86,9 @@ class MediaFragment(private val mediaId: Int) : Fragment() { private fun updateGUI() = with(model) { // generic gui val backdropUrl = tmdbResult?.backdropPath?.let { TMDBApiController.imageUrl + it } - ?: media2.posterURL + ?: media.posterURL val posterUrl = tmdbResult?.posterPath?.let { TMDBApiController.imageUrl + it } - ?: media2.posterURL + ?: media.posterURL // load poster and backdrop Glide.with(requireContext()).load(posterUrl) @@ -99,13 +98,13 @@ class MediaFragment(private val mediaId: Int) : Fragment() { .apply(RequestOptions.bitmapTransform(BlurTransformation(20, 3))) .into(binding.imageBackdrop) - binding.textTitle.text = media2.title - binding.textYear.text = media2.year.toString() - binding.textAge.text = media2.age.toString() - binding.textOverview.text = media2.shortText + binding.textTitle.text = media.title + binding.textYear.text = media.year.toString() + binding.textAge.text = media.age.toString() + binding.textOverview.text = media.shortText // set "my list" indicator - if (StorageController.myList.contains(media2.aodId)) { + if (StorageController.myList.contains(media.aodId)) { Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(binding.imageMyListAction) } else { Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(binding.imageMyListAction) @@ -116,25 +115,25 @@ class MediaFragment(private val mediaId: Int) : Fragment() { pagerAdapter.notifyDataSetChanged() // specific gui - if (media2.type == MediaType.TVSHOW) { + if (media.type == MediaType.TVSHOW) { // get next episode - nextEpisodeId = media2.playlist.firstOrNull{ !it.watched }?.mediaId - ?: media2.playlist.first().mediaId + nextEpisodeId = media.playlist.firstOrNull{ !it.watched }?.mediaId + ?: media.playlist.first().mediaId // title is the next episodes title - binding.textTitle.text = media2.getEpisodeById(nextEpisodeId).title + binding.textTitle.text = media.getEpisodeById(nextEpisodeId).title // episodes count binding.textEpisodesOrRuntime.text = resources.getQuantityString( R.plurals.text_episodes_count, - media2.playlist.size, - media2.playlist.size + media.playlist.size, + media.playlist.size ) // episodes fragments.add(MediaFragmentEpisodes()) pagerAdapter.notifyDataSetChanged() - } else if (media2.type == MediaType.MOVIE) { + } else if (media.type == MediaType.MOVIE) { val tmdbMovie = (tmdbResult as TMDBMovie?) if (tmdbMovie?.runtime != null) { @@ -149,7 +148,7 @@ class MediaFragment(private val mediaId: Int) : Fragment() { } // if has similar titles - if (media2.similar.isNotEmpty()) { + if (media.similar.isNotEmpty()) { fragments.add(MediaFragmentSimilar()) pagerAdapter.notifyDataSetChanged() } @@ -165,20 +164,20 @@ class MediaFragment(private val mediaId: Int) : Fragment() { private fun initActions() = with(model) { binding.buttonPlay.setOnClickListener { - when (media2.type) { - MediaType.MOVIE -> playEpisode(media2.playlist.first().mediaId) + when (media.type) { + MediaType.MOVIE -> playEpisode(media.playlist.first().mediaId) MediaType.TVSHOW -> playEpisode(nextEpisodeId) - else -> Log.e(javaClass.name, "Wrong Type: ${media2.type}") + else -> Log.e(javaClass.name, "Wrong Type: ${media.type}") } } // add or remove media from myList binding.linearMyListAction.setOnClickListener { - if (StorageController.myList.contains(media2.aodId)) { - StorageController.myList.remove(media2.aodId) + if (StorageController.myList.contains(media.aodId)) { + StorageController.myList.remove(media.aodId) Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(binding.imageMyListAction) } else { - StorageController.myList.add(media2.aodId) + StorageController.myList.add(media.aodId) Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(binding.imageMyListAction) } StorageController.saveMyList(requireContext()) @@ -195,7 +194,7 @@ class MediaFragment(private val mediaId: Int) : Fragment() { * TODO this is also used in MediaFragmentEpisode, we should only have on implementation */ private fun playEpisode(episodeId: Int) { - (activity as MainActivity).startPlayer(model.media2.aodId, episodeId) + (activity as MainActivity).startPlayer(model.media.aodId, episodeId) Log.d(javaClass.name, "Started Player with episodeId: $episodeId") model.updateNextEpisode(episodeId) // set the correct next episode 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 7a0eff9..f2e9f58 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 @@ -27,13 +27,13 @@ class MediaFragmentEpisodes : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - adapterRecEpisodes = EpisodeItemAdapter(model.media2.playlist, model.tmdbTVSeason?.episodes) + adapterRecEpisodes = EpisodeItemAdapter(model.media.playlist, model.tmdbTVSeason?.episodes) binding.recyclerEpisodes.adapter = adapterRecEpisodes // set onItemClick only in adapter is initialized if (this::adapterRecEpisodes.isInitialized) { adapterRecEpisodes.onImageClick = { _, position -> - playEpisode(model.media2.playlist[position].mediaId) + playEpisode(model.media.playlist[position].mediaId) } } } @@ -43,7 +43,7 @@ class MediaFragmentEpisodes : Fragment() { // if adapterRecEpisodes is initialized, update the watched state for the episodes if (this::adapterRecEpisodes.isInitialized) { - model.media2.playlist.forEachIndexed { index, episodeInfo -> + model.media.playlist.forEachIndexed { index, episodeInfo -> adapterRecEpisodes.updateWatchedState(episodeInfo.watched, index) } adapterRecEpisodes.notifyDataSetChanged() @@ -51,7 +51,7 @@ class MediaFragmentEpisodes : Fragment() { } private fun playEpisode(episodeId: Int) { - (activity as MainActivity).startPlayer(model.media2.aodId, episodeId) + (activity as MainActivity).startPlayer(model.media.aodId, episodeId) Log.d(javaClass.name, "Started Player with episodeId: $episodeId") model.updateNextEpisode(episodeId) // set the correct next episode diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt index dba70c3..87195a1 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt @@ -27,7 +27,7 @@ class MediaFragmentSimilar : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - adapterSimilar = MediaItemAdapter(model.media2.similar) + adapterSimilar = MediaItemAdapter(model.media.similar) binding.recyclerMediaSimilar.adapter = adapterSimilar binding.recyclerMediaSimilar.addItemDecoration(MediaItemDecoration(9)) 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 fd1f4b6..95d9887 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 @@ -16,17 +16,11 @@ import org.mosad.teapod.util.tmdb.TMDBTVSeason */ class MediaFragmentViewModel(application: Application) : AndroidViewModel(application) { -// var media = Media(-1, "", MediaType.OTHER) -// internal set -// var nextEpisode = Episode() -// internal set - - var media2 = AoDMediaNone + var media = AoDMediaNone internal set var nextEpisodeId = -1 internal set - var tmdbResult: TMDBResult? = null // TODO rename internal set var tmdbTVSeason: TMDBTVSeason? =null @@ -38,18 +32,17 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic * set media, tmdb and nextEpisode * TODO run aod and tmdb load parallel */ - suspend fun load(mediaId: Int) { + suspend fun load(aodId: Int) { val tmdbApiController = TMDBApiController() - //media = AoDParser.getMediaById(mediaId) - media2 = AoDParser.getMediaById2(mediaId) + media = AoDParser.getMediaById(aodId) // check if metaDB knows the title - val tmdbId: Int = if (MetaDBController.mediaList.media.contains(media2.aodId)) { + val tmdbId: Int = if (MetaDBController.mediaList.media.contains(aodId)) { // load media info from metaDB val metaDB = MetaDBController() - mediaMeta = when (media2.type) { - MediaType.MOVIE -> metaDB.getMovieMetadata(media2.aodId) - MediaType.TVSHOW -> metaDB.getTVShowMetadata(media2.aodId) + mediaMeta = when (media.type) { + MediaType.MOVIE -> metaDB.getMovieMetadata(media.aodId) + MediaType.TVSHOW -> metaDB.getTVShowMetadata(media.aodId) else -> null } @@ -57,28 +50,27 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic } else { // use tmdb search to get media info mediaMeta = null // set mediaMeta to null, if metaDB doesn't know the media - tmdbApiController.search(stripTitleInfo(media2.title), media2.type) + tmdbApiController.search(stripTitleInfo(media.title), media.type) } - tmdbResult = when (media2.type) { + tmdbResult = when (media.type) { MediaType.MOVIE -> tmdbApiController.getMovieDetails(tmdbId) MediaType.TVSHOW -> tmdbApiController.getTVShowDetails(tmdbId) else -> null } - println(tmdbResult) // TODO // get season info, if metaDB knows the tv show - tmdbTVSeason = if (media2.type == MediaType.TVSHOW && mediaMeta != null) { + tmdbTVSeason = if (media.type == MediaType.TVSHOW && mediaMeta != null) { val tvShowMeta = mediaMeta as TVShowMeta tmdbApiController.getTVSeasonDetails(tvShowMeta.tmdbId, tvShowMeta.tmdbSeasonNumber) } else { null } - if (media2.type == MediaType.TVSHOW) { + if (media.type == MediaType.TVSHOW) { //nextEpisode = media.episodes.firstOrNull{ !it.watched } ?: media.episodes.first() - nextEpisodeId = media2.playlist.firstOrNull { !it.watched }?.mediaId - ?: media2.playlist.first().mediaId + nextEpisodeId = media.playlist.firstOrNull { !it.watched }?.mediaId + ?: media.playlist.first().mediaId } } @@ -87,13 +79,10 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic * if no matching is found, use first episode */ fun updateNextEpisode(episodeId: Int) { - if (media2.type == MediaType.MOVIE) return // return if movie + if (media.type == MediaType.MOVIE) return // return if movie -// nextEpisode = media.episodes.firstOrNull{ it.number > currentEp.number } -// ?: media.episodes.first() - - nextEpisodeId = media2.playlist.firstOrNull { it.number > media2.getEpisodeById(episodeId).number }?.mediaId - ?: media2.playlist.first().mediaId + nextEpisodeId = media.playlist.firstOrNull { it.number > media.getEpisodeById(episodeId).number }?.mediaId + ?: media.playlist.first().mediaId } // remove unneeded info from the media title before searching diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerActivity.kt b/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerActivity.kt index 24682f5..a1307d8 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerActivity.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerActivity.kt @@ -171,7 +171,7 @@ class PlayerActivity : AppCompatActivity() { } private fun initPlayer() { - if (model.media.id < 0) { + if (model.media.aodId < 0) { Log.e(javaClass.name, "No media was set.") this.finish() } 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 2894e21..9dffdd1 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 @@ -40,11 +40,11 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) val currentEpisodeChangedListener = ArrayList<() -> Unit>() private val preferredLanguage = if (Preferences.preferSecondary) Locale.JAPANESE else Locale.GERMAN - var media: Media = Media(-1, "", DataTypes.MediaType.OTHER) + var media: AoDMedia = AoDMediaNone internal set - var currentEpisode = Episode() + var currentEpisode = AoDEpisodeNone internal set - var nextEpisode: Episode? = null + var nextEpisode: AoDEpisode? = null internal set var mediaMeta: Meta? = null internal set @@ -80,12 +80,12 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) fun loadMedia(mediaId: Int, episodeId: Int) { runBlocking { media = AoDParser.getMediaById(mediaId) - mediaMeta = loadMediaMeta(media.id) // can be done blocking, since it should be cached + mediaMeta = loadMediaMeta(media.aodId) // can be done blocking, since it should be cached } currentEpisode = media.getEpisodeById(episodeId) nextEpisode = selectNextEpisode() - currentEpisodeMeta = getEpisodeMetaByAoDMediaId(currentEpisode.id) + currentEpisodeMeta = getEpisodeMetaByAoDMediaId(currentEpisode.mediaId) currentLanguage = currentEpisode.getPreferredStream(preferredLanguage).language } @@ -122,12 +122,12 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) * * updateWatchedState for the next (now current) episode */ - fun playEpisode(episode: Episode, replace: Boolean = false, seekPosition: Long = 0) { + fun playEpisode(episode: AoDEpisode, replace: Boolean = false, seekPosition: Long = 0) { val preferredStream = episode.getPreferredStream(currentLanguage) currentLanguage = preferredStream.language // update current language, since it may have changed currentEpisode = episode nextEpisode = selectNextEpisode() - currentEpisodeMeta = getEpisodeMetaByAoDMediaId(episode.id) + currentEpisodeMeta = getEpisodeMetaByAoDMediaId(episode.mediaId) currentEpisodeChangedListener.forEach { it() } // update player gui (title) val mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource( @@ -138,7 +138,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) // if episodes has not been watched, mark as watched if (!episode.watched) { viewModelScope.launch { - AoDParser.markAsWatched(media.id, episode.id) + AoDParser.markAsWatched(media.aodId, episode.mediaId) } } } @@ -188,13 +188,8 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) * Based on the current episodeId, get the next episode. If there is no next * episode, return null */ - private fun selectNextEpisode(): Episode? { - val nextEpIndex = media.episodes.indexOfFirst { it.id == currentEpisode.id } + 1 - return if (nextEpIndex < media.episodes.size) { - media.episodes[nextEpIndex] - } else { - null - } + private fun selectNextEpisode(): AoDEpisode? { + return media.playlist.firstOrNull { it.number > media.getEpisodeById(currentEpisode.mediaId).number } } } \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt b/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt index cb51deb..f7fe649 100644 --- a/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt +++ b/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt @@ -28,11 +28,11 @@ class EpisodesListPlayer @JvmOverloads constructor( } model?.let { - adapterRecEpisodes = PlayerEpisodeItemAdapter(model.media.episodes) + adapterRecEpisodes = PlayerEpisodeItemAdapter(model.media.playlist) adapterRecEpisodes.onImageClick = { _, position -> (this.parent as ViewGroup).removeView(this) - model.playEpisode(model.media.episodes[position], replace = true) + model.playEpisode(model.media.playlist[position], replace = true) } adapterRecEpisodes.currentSelected = model.currentEpisode.number - 1 diff --git a/app/src/main/java/org/mosad/teapod/util/DataTypes.kt b/app/src/main/java/org/mosad/teapod/util/DataTypes.kt index 909e766..dea2d20 100644 --- a/app/src/main/java/org/mosad/teapod/util/DataTypes.kt +++ b/app/src/main/java/org/mosad/teapod/util/DataTypes.kt @@ -82,6 +82,11 @@ data class AoDEpisode( ?: streams.first() } +data class Stream( + val url: String, + val language : Locale +) + // TODO will be watched info (state and callback) -> remove description and number data class AoDEpisodeInfo( val aodMediaId: Int, @@ -114,62 +119,6 @@ val AoDEpisodeNone = AoDEpisode( mutableListOf() ) -// LEGACY - -data class Media( - val id: Int, - val link: String, - val type: DataTypes.MediaType, - val info: Info = Info(), - val episodes: ArrayList = arrayListOf() -) { - fun hasEpisode(id: Int) = episodes.any { it.id == id } - fun getEpisodeById(id: Int) = episodes.first { it.id == id } -} - -/** - * uses var, since the values are written in different steps - */ -data class Info( - var title: String = "", - var posterUrl: String = "", - var shortDesc: String = "", - var description: String = "", - var year: Int = 0, - var age: Int = 0, - var episodesCount: Int = 0, - var similar: List = listOf() -) - -/** - * number = episode number (0..n) - */ -data class Episode( - val id: Int = -1, - val streams: MutableList = mutableListOf(), - val title: String = "", - val posterUrl: String = "", - val description: String = "", - var shortDesc: String = "", - val number: Int = 0, - var watched: Boolean = false, - var watchedCallback: String = "" -) { - /** - * get the preferred stream - * @return the preferred stream, if not present use the first stream - */ - fun getPreferredStream(language: Locale) = - streams.firstOrNull { it.language == language } ?: streams.first() - - fun hasDub() = streams.any { it.language == Locale.GERMAN } -} - -data class Stream( - val url: String, - val language : Locale -) - /** * this class is used to represent the aod json API? */ diff --git a/app/src/main/java/org/mosad/teapod/util/adapter/PlayerEpisodeItemAdapter.kt b/app/src/main/java/org/mosad/teapod/util/adapter/PlayerEpisodeItemAdapter.kt index 8b005a7..e1d37d5 100644 --- a/app/src/main/java/org/mosad/teapod/util/adapter/PlayerEpisodeItemAdapter.kt +++ b/app/src/main/java/org/mosad/teapod/util/adapter/PlayerEpisodeItemAdapter.kt @@ -9,9 +9,9 @@ import com.bumptech.glide.request.RequestOptions import jp.wasabeef.glide.transformations.RoundedCornersTransformation import org.mosad.teapod.R import org.mosad.teapod.databinding.ItemEpisodePlayerBinding -import org.mosad.teapod.util.Episode +import org.mosad.teapod.util.AoDEpisode -class PlayerEpisodeItemAdapter(private val episodes: List) : RecyclerView.Adapter() { +class PlayerEpisodeItemAdapter(private val episodes: List) : RecyclerView.Adapter() { var onImageClick: ((String, Int) -> Unit)? = null var currentSelected: Int = -1 // -1, since position should never be < 0 @@ -33,8 +33,8 @@ class PlayerEpisodeItemAdapter(private val episodes: List) : RecyclerVi holder.binding.textEpisodeTitle2.text = titleText holder.binding.textEpisodeDesc2.text = ep.shortDesc - if (episodes[position].posterUrl.isNotEmpty()) { - Glide.with(context).load(ep.posterUrl) + if (ep.imageURL.isNotEmpty()) { + Glide.with(context).load(ep.imageURL) .apply(RequestOptions.bitmapTransform(RoundedCornersTransformation(10, 0))) .into(holder.binding.imageEpisode) }