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 eb5b2d5..7fbe2c1 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 @@ -34,6 +34,7 @@ import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import kotlinx.coroutines.* import kotlinx.serialization.SerializationException +import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject @@ -52,6 +53,7 @@ object Crunchyroll { } } private const val baseUrl = "https://beta-api.crunchyroll.com" + private const val staticUrl = "https://static.crunchyroll.com" private const val basicApiTokenUrl = "https://gitlab.com/-/snippets/2274956/raw/main/snippetfile1.txt" private var basicApiToken: String = "" @@ -164,12 +166,15 @@ object Crunchyroll { } } + /** + * Send a HTTP GET request with [params] to the [endpoint] at [url], if url is empty use baseUrl + */ private suspend inline fun requestGet( endpoint: String, params: List> = listOf(), url: String = "" ): T { - val path = url.ifEmpty { "$baseUrl$endpoint" } + val path = url.ifEmpty { baseUrl }.plus(endpoint) return request(path, HttpMethod.Get, params) } @@ -583,6 +588,23 @@ object Crunchyroll { } } + /** + * Get the intro meta data including start, end and duration of the intro. + * + * @param episodeId A episode ID as strings. + */ + suspend fun datalabIntro(episodeId: String): DatalabIntro { + val datalabIntroEndpoint = "/datalab-intro-v2/$episodeId.json" + + return try { + val response: HttpResponse = requestGet(datalabIntroEndpoint, url = staticUrl) + Json.decodeFromString(response.bodyAsText()) + } catch (ex: SerializationException) { + Log.e(TAG, "SerializationException in datalabIntro(). EpisodeId=$episodeId", ex) + NoneDatalabIntro + } + } + /** * Get similar media for a show/movie. * 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 0dda7db..e134d46 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 @@ -347,6 +347,22 @@ data class PlayheadObject( @SerialName("last_modified") val lastModified: String, ) +/** + * Meta data for a episode intro. All time values are in seconds. + */ +@Serializable +data class DatalabIntro( + @SerialName("media_id") val mediaId: String, + @SerialName("startTime") val startTime: Float, + @SerialName("endTime") val endTime: Float, + @SerialName("duration") val duration: Float, + @SerialName("comparedWith") val comparedWith: String, + @SerialName("ordering") val ordering: String, + @SerialName("last_updated") val lastUpdated: String, +) + +val NoneDatalabIntro = DatalabIntro("", 0f, 0f, 0f, "", "", "") + /** * playback/stream data classes */ 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 251efd5..48068f4 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 @@ -325,19 +325,18 @@ class PlayerActivity : AppCompatActivity() { hideButtonNextEp() } - // if meta data is present and opening_start & opening_duration are valid, show skip opening - model.currentEpisodeMeta?.let { - if (it.openingDuration > 0 && - currentPosition in it.openingStart..(it.openingStart + 10000) && - !playerBinding.buttonSkipOp.isVisible - ) { + // into metadata is present and we can show the skip button + if (model.currentIntroMetadata.duration >= 10) { + val startTime = model.currentIntroMetadata.startTime.toInt() * 1000 + if (currentPosition in startTime..(startTime + 10000) && !playerBinding.buttonSkipOp.isVisible) { showButtonSkipOp() } else if (playerBinding.buttonSkipOp.isVisible && - currentPosition !in it.openingStart..(it.openingStart + 10000) + currentPosition !in startTime..(startTime + 10000) ) { - // the button should only be visible, if currentEpisodeMeta != null + // the button should only be visible if currentEpisodeMeta != null hideButtonSkipOp() } + } // if controls are visible, update them @@ -452,8 +451,9 @@ class PlayerActivity : AppCompatActivity() { private fun skipOpening() { // calculate the seek time - model.currentEpisodeMeta?.let { - val seekTime = (it.openingStart + it.openingDuration) - model.player.currentPosition + if (model.currentIntroMetadata.duration > 10) { + val endTime = model.currentIntroMetadata.endTime.toInt() * 1000 + val seekTime = endTime - model.player.currentPosition model.seekToOffset(seekTime) } } 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 e417ac9..c009851 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 @@ -65,6 +65,8 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) internal set var currentPlayheads: PlayheadsMap = mutableMapOf() internal set + var currentIntroMetadata: DatalabIntro = NoneDatalabIntro + internal set // var tmdbTVSeason: TMDBTVSeason? =null // internal set @@ -148,6 +150,10 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) // player actions + /** + * Seeks to a offset position specified in milliseconds in the current MediaItem. + * @param offset The offset position in the current MediaItem. + */ fun seekToOffset(offset: Long) { player.seekTo(player.currentPosition + offset) } @@ -201,6 +207,9 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) (it.playhead.times(1000)).toLong() } } + }, + viewModelScope.launch(Dispatchers.IO) { + currentIntroMetadata = Crunchyroll.datalabIntro(currentEpisode.id) } ) }