add crunchy intro metadata to parser and update the skip intro function, closes #66
This commit is contained in:
parent
6624e71228
commit
71d5c58653
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue