add crunchy intro metadata to parser and update the skip intro function, closes #66

This commit is contained in:
Jannik 2022-10-28 22:57:42 +02:00
parent 6624e71228
commit 71d5c58653
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
4 changed files with 58 additions and 11 deletions

View File

@ -34,6 +34,7 @@ import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.* import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
@ -52,6 +53,7 @@ object Crunchyroll {
} }
} }
private const val baseUrl = "https://beta-api.crunchyroll.com" 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 const val basicApiTokenUrl = "https://gitlab.com/-/snippets/2274956/raw/main/snippetfile1.txt"
private var basicApiToken: String = "" 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 <reified T> requestGet( private suspend inline fun <reified T> requestGet(
endpoint: String, endpoint: String,
params: List<Pair<String, Any?>> = listOf(), params: List<Pair<String, Any?>> = listOf(),
url: String = "" url: String = ""
): T { ): T {
val path = url.ifEmpty { "$baseUrl$endpoint" } val path = url.ifEmpty { baseUrl }.plus(endpoint)
return request(path, HttpMethod.Get, params) 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. * Get similar media for a show/movie.
* *

View File

@ -347,6 +347,22 @@ data class PlayheadObject(
@SerialName("last_modified") val lastModified: String, @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 * playback/stream data classes
*/ */

View File

@ -325,19 +325,18 @@ class PlayerActivity : AppCompatActivity() {
hideButtonNextEp() hideButtonNextEp()
} }
// if meta data is present and opening_start & opening_duration are valid, show skip opening // into metadata is present and we can show the skip button
model.currentEpisodeMeta?.let { if (model.currentIntroMetadata.duration >= 10) {
if (it.openingDuration > 0 && val startTime = model.currentIntroMetadata.startTime.toInt() * 1000
currentPosition in it.openingStart..(it.openingStart + 10000) && if (currentPosition in startTime..(startTime + 10000) && !playerBinding.buttonSkipOp.isVisible) {
!playerBinding.buttonSkipOp.isVisible
) {
showButtonSkipOp() showButtonSkipOp()
} else if (playerBinding.buttonSkipOp.isVisible && } 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() hideButtonSkipOp()
} }
} }
// if controls are visible, update them // if controls are visible, update them
@ -452,8 +451,9 @@ class PlayerActivity : AppCompatActivity() {
private fun skipOpening() { private fun skipOpening() {
// calculate the seek time // calculate the seek time
model.currentEpisodeMeta?.let { if (model.currentIntroMetadata.duration > 10) {
val seekTime = (it.openingStart + it.openingDuration) - model.player.currentPosition val endTime = model.currentIntroMetadata.endTime.toInt() * 1000
val seekTime = endTime - model.player.currentPosition
model.seekToOffset(seekTime) model.seekToOffset(seekTime)
} }
} }

View File

@ -65,6 +65,8 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
internal set internal set
var currentPlayheads: PlayheadsMap = mutableMapOf() var currentPlayheads: PlayheadsMap = mutableMapOf()
internal set internal set
var currentIntroMetadata: DatalabIntro = NoneDatalabIntro
internal set
// var tmdbTVSeason: TMDBTVSeason? =null // var tmdbTVSeason: TMDBTVSeason? =null
// internal set // internal set
@ -148,6 +150,10 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
// player actions // 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) { fun seekToOffset(offset: Long) {
player.seekTo(player.currentPosition + offset) player.seekTo(player.currentPosition + offset)
} }
@ -201,6 +207,9 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
(it.playhead.times(1000)).toLong() (it.playhead.times(1000)).toLong()
} }
} }
},
viewModelScope.launch(Dispatchers.IO) {
currentIntroMetadata = Crunchyroll.datalabIntro(currentEpisode.id)
} }
) )
} }