From d40ab9519ccd82d08789c26d31684b2a3202c5cb Mon Sep 17 00:00:00 2001 From: Jannik Date: Sun, 19 Feb 2023 16:53:54 +0100 Subject: [PATCH] migrate playheads() to crunchyroll v2 api --- .../teapod/parser/crunchyroll/Crunchyroll.kt | 18 ++++++----- .../teapod/parser/crunchyroll/DataTypes.kt | 13 +++++--- .../main/viewmodel/MediaFragmentViewModel.kt | 32 ++++++++++--------- .../ui/activity/player/PlayerViewModel.kt | 11 ++++--- .../fragment/EpisodeListDialogFragment.kt | 2 +- .../main/java/org/mosad/teapod/util/Utils.kt | 6 ++++ .../teapod/util/adapter/EpisodeItemAdapter.kt | 3 +- 7 files changed, 50 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/org/mosad/teapod/parser/crunchyroll/Crunchyroll.kt b/app/src/main/java/org/mosad/teapod/parser/crunchyroll/Crunchyroll.kt index a42dd15..10693b7 100644 --- a/app/src/main/java/org/mosad/teapod/parser/crunchyroll/Crunchyroll.kt +++ b/app/src/main/java/org/mosad/teapod/parser/crunchyroll/Crunchyroll.kt @@ -567,18 +567,20 @@ object Crunchyroll { * @param episodeIDs A **[List]** of episodes IDs as strings. * @return A **[Map]** containing playback info. */ - suspend fun playheads(episodeIDs: List): PlayheadsMap { - val playheadsEndpoint = "/content/v1/playheads/$accountID/${episodeIDs.joinToString(",")}" - val parameters = listOf("locale" to Preferences.preferredSubtitleLocale.toLanguageTag()) + suspend fun playheads(episodeIDs: List): Playheads { + val playheadsEndpoint = "/content/v2/$accountID/playheads" + val parameters = listOf( + "content_ids" to episodeIDs.joinToString(","), + "preferred_audio_language" to Preferences.preferredAudioLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag() + ) + return try { requestGet(playheadsEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in playheads().", ex) - emptyMap() - } catch (ex: Throwable) { + } catch (ex: Exception) { Log.e(TAG, "Exception in playheads().", ex.cause) - emptyMap() + NonePlayheads } } diff --git a/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt b/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt index 591a970..5a43200 100644 --- a/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt +++ b/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt @@ -127,9 +127,6 @@ typealias SearchResult = Collection typealias SearchCollection = Collection typealias BrowseResult = Collection typealias SimilarToResult = Collection -typealias Watchlist = Collection2 -typealias HistoryList = Collection2 -typealias UpNextSeriesList = Collection2 typealias RecommendationsList = Collection typealias Benefits = Collection @@ -159,9 +156,13 @@ data class Images(val poster_tall: List>, val poster_wide: List +typealias HistoryList = Collection2 +typealias UpNextSeriesList = Collection2 + @Serializable data class WatchlistItem( @SerialName("panel") val panel: EpisodePanel, @@ -352,7 +353,7 @@ val NoneVersion = Version( variant = "" ) -typealias PlayheadsMap = Map +typealias Playheads = Collection2 @Serializable data class PlayheadObject( @@ -362,6 +363,8 @@ data class PlayheadObject( @SerialName("last_modified") val lastModified: String, ) +val NonePlayheads = Playheads(0, emptyList()) + /** * Meta data for a episode intro. All time values are in seconds. */ 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 aed13e4..4cfb28a 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 @@ -3,11 +3,13 @@ package org.mosad.teapod.ui.activity.main.viewmodel import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.async import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import org.mosad.teapod.parser.crunchyroll.* import org.mosad.teapod.util.DataTypes.MediaType import org.mosad.teapod.util.tmdb.* +import org.mosad.teapod.util.toPlayheadsMap /** * handle media, next ep and tmdb @@ -25,7 +27,8 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic internal set val currentEpisodesCrunchy = arrayListOf() // used for EpisodeItemAdapter (easier updates) - // additional media info + // additional media info, might change during during user interaction + // use a map to update the episode adapter values val currentPlayheads: MutableMap = mutableMapOf() var isWatchlist = false internal set @@ -80,12 +83,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic // load playheads and tmdb in parallel listOf( - viewModelScope.launch { - // get playheads (including fully watched state) - val episodeIDs = episodesCrunchy.data.map { it.id } - currentPlayheads.clear() - currentPlayheads.putAll(Crunchyroll.playheads(episodeIDs)) - }, + updatePlayheadsAsync(), viewModelScope.launch { loadTmdbInfo() } // use tmdb search to get media info ).joinAll() } @@ -117,6 +115,16 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic // } else NoneTMDBTVSeason } + /** + * Get current playheads for all episodes + */ + private fun updatePlayheadsAsync() = viewModelScope.async { + currentPlayheads.clear() + currentPlayheads.putAll( + Crunchyroll.playheads(episodesCrunchy.data.map { it.id }).toPlayheadsMap() + ) + } + /** * Set currentSeasonCrunchy based on the season id. Also set the new seasons episodes. * @@ -137,9 +145,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic currentEpisodesCrunchy.addAll(episodesCrunchy.data) // update playheads playheads (including fully watched state) - val episodeIDs = episodesCrunchy.data.map { it.id } - currentPlayheads.clear() - currentPlayheads.putAll(Crunchyroll.playheads(episodeIDs)) + updatePlayheadsAsync().await() } suspend fun setWatchlist() { @@ -154,11 +160,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic suspend fun updateOnResume() { joinAll( - viewModelScope.launch { - val episodeIDs = episodesCrunchy.data.map { it.id } - currentPlayheads.clear() - currentPlayheads.putAll(Crunchyroll.playheads(episodeIDs)) - }, + updatePlayheadsAsync(), viewModelScope.launch { upNextSeries = Crunchyroll.upNextSeries(seriesCrunchy.id) } ) } 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 8545174..9e515c7 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,6 +40,7 @@ import org.mosad.teapod.util.metadb.EpisodeMeta import org.mosad.teapod.util.metadb.Meta import org.mosad.teapod.util.metadb.MetaDBController import org.mosad.teapod.util.metadb.TVShowMeta +import org.mosad.teapod.util.toPlayheadsMap import java.util.* import kotlin.concurrent.scheduleAtFixedRate @@ -63,7 +64,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) internal set var currentEpisodeMeta: EpisodeMeta? = null internal set - var currentPlayheads: PlayheadsMap = mutableMapOf() + var currentPlayheads = mapOf() internal set var currentIntroMetadata: DatalabIntro = NoneDatalabIntro internal set @@ -142,7 +143,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) viewModelScope.launch { mediaMeta = loadMediaMeta(episodes.data.first().seriesId) }, viewModelScope.launch { val episodeIDs = episodes.data.map { it.id } - currentPlayheads = Crunchyroll.playheads(episodeIDs) + currentPlayheads = Crunchyroll.playheads(episodeIDs).toPlayheadsMap() } ).joinAll() Log.d(classTag, "meta: $mediaMeta") @@ -233,7 +234,9 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) Log.d(classTag, currentVersion.toString()) }, viewModelScope.launch(Dispatchers.IO) { - Crunchyroll.playheads(listOf(currentEpisode.id))[currentEpisode.id]?.let { + Crunchyroll.playheads(listOf(currentEpisode.id)).data.firstOrNull { + it.contentId == currentEpisode.id + }?.let { // if the episode was fully watched, start at the beginning currentPlayhead = if (it.fullyWatched) { 0 @@ -330,7 +333,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) viewModelScope.launch { val episodeIDs = episodes.data.map { it.id } - currentPlayheads = Crunchyroll.playheads(episodeIDs) + currentPlayheads = Crunchyroll.playheads(episodeIDs).toPlayheadsMap() } } diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/player/fragment/EpisodeListDialogFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/player/fragment/EpisodeListDialogFragment.kt index 01d02d3..28554bb 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/player/fragment/EpisodeListDialogFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/player/fragment/EpisodeListDialogFragment.kt @@ -44,7 +44,7 @@ class EpisodeListDialogFragment : DialogFragment() { val adapterRecEpisodes = EpisodeItemAdapter( model.episodes.data, null, - model.currentPlayheads.toMap(), + model.currentPlayheads, EpisodeItemAdapter.OnClickListener { episode -> dismiss() // TODO make this none blocking, if necessary? diff --git a/app/src/main/java/org/mosad/teapod/util/Utils.kt b/app/src/main/java/org/mosad/teapod/util/Utils.kt index 6495176..ae2f634 100644 --- a/app/src/main/java/org/mosad/teapod/util/Utils.kt +++ b/app/src/main/java/org/mosad/teapod/util/Utils.kt @@ -10,7 +10,9 @@ import androidx.core.view.WindowInsetsControllerCompat import androidx.fragment.app.Fragment import org.mosad.teapod.R import org.mosad.teapod.parser.crunchyroll.Collection +import org.mosad.teapod.parser.crunchyroll.Collection2 import org.mosad.teapod.parser.crunchyroll.Item +import org.mosad.teapod.parser.crunchyroll.PlayheadObject import org.mosad.teapod.ui.activity.player.PlayerActivity import java.util.* @@ -57,6 +59,10 @@ fun Locale.toDisplayString(fallback: String): String { } } +fun Collection2.toPlayheadsMap(): Map { + return this.data.associateBy { it.contentId } +} + fun hideBars(window: Window?, root: View) { if (window != null) { WindowCompat.setDecorFitsSystemWindows(window, false) 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 3cdbf30..73d195c 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 @@ -16,13 +16,12 @@ import org.mosad.teapod.databinding.ItemEpisodeBinding import org.mosad.teapod.databinding.ItemEpisodePlayerBinding import org.mosad.teapod.parser.crunchyroll.Episode import org.mosad.teapod.parser.crunchyroll.PlayheadObject -import org.mosad.teapod.parser.crunchyroll.PlayheadsMap import org.mosad.teapod.util.tmdb.TMDBTVEpisode class EpisodeItemAdapter( private val episodes: List, private val tmdbEpisodes: List?, - private val playheads: PlayheadsMap, + private val playheads: Map, private val onClickListener: OnClickListener, private val viewType: ViewType ) : RecyclerView.Adapter() {