Implement media fragment for tv shows
This commit is contained in:
@ -11,12 +11,13 @@ import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.*
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
object Crunchyroll {
|
||||
|
||||
private val baseUrl = "https://beta-api.crunchyroll.com"
|
||||
private const val baseUrl = "https://beta-api.crunchyroll.com"
|
||||
|
||||
private var accessToken = ""
|
||||
private var tokenType = ""
|
||||
@ -25,9 +26,14 @@ object Crunchyroll {
|
||||
private var signature = ""
|
||||
private var keyPairID = ""
|
||||
|
||||
// TODO temp helper vary
|
||||
var locale = "${Locale.GERMANY.language}-${Locale.GERMANY.country}"
|
||||
var country = Locale.GERMANY.country
|
||||
|
||||
val browsingCache = arrayListOf<Item>()
|
||||
|
||||
fun login(username: String, password: String): Boolean = runBlocking {
|
||||
val tokenEndpoint = "/auth/v1/token"
|
||||
|
||||
val formData = listOf(
|
||||
"username" to username,
|
||||
"password" to password,
|
||||
@ -63,9 +69,15 @@ object Crunchyroll {
|
||||
}
|
||||
|
||||
// TODO get/post difference
|
||||
private suspend fun request(endpoint: String, params: Parameters = listOf()): Result<FuelJson, FuelError> = coroutineScope {
|
||||
private suspend fun request(
|
||||
endpoint: String,
|
||||
params: Parameters = listOf(),
|
||||
url: String = ""
|
||||
): Result<FuelJson, FuelError> = coroutineScope {
|
||||
val path = if (url.isEmpty()) "$baseUrl$endpoint" else url
|
||||
|
||||
return@coroutineScope (Dispatchers.IO) {
|
||||
val (request, response, result) = Fuel.get("$baseUrl$endpoint", params)
|
||||
val (request, response, result) = Fuel.get(path, params)
|
||||
.header("Authorization", "$tokenType $accessToken")
|
||||
.responseJson()
|
||||
|
||||
@ -77,42 +89,6 @@ object Crunchyroll {
|
||||
}
|
||||
}
|
||||
|
||||
// TESTING
|
||||
|
||||
|
||||
// TODO sort_by, default alphabetical, n, locale de-DE, categories
|
||||
/**
|
||||
* Browse the media available on crunchyroll.
|
||||
*
|
||||
* @param sortBy
|
||||
* @param n Number of items to return, defaults to 10
|
||||
*
|
||||
* @return A **[BrowseResult]** object is returned.
|
||||
*/
|
||||
suspend fun browse(sortBy: SortBy = SortBy.ALPHABETICAL, n: Int = 10): BrowseResult {
|
||||
val browseEndpoint = "/content/v1/browse"
|
||||
val parameters = listOf("sort_by" to sortBy.str, "n" to n)
|
||||
|
||||
val result = request(browseEndpoint, parameters)
|
||||
|
||||
// val browseResult = json.decodeFromString<BrowseResult>(result.component1()?.obj()?.toString()!!)
|
||||
// println(browseResult.items.size)
|
||||
|
||||
return json.decodeFromString(result.component1()?.obj()?.toString()!!)
|
||||
}
|
||||
|
||||
// TODO
|
||||
suspend fun search() {
|
||||
val searchEndpoint = "/content/v1/search"
|
||||
val result = request(searchEndpoint)
|
||||
|
||||
println("${result.component1()?.obj()?.get("total")}")
|
||||
|
||||
val test = json.decodeFromString<BrowseResult>(result.component1()?.obj()?.toString()!!)
|
||||
println(test.items.size)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the identifiers necessary for streaming. If the identifiers are
|
||||
* retrieved, set the corresponding global var. The identifiers are valid for 24h.
|
||||
@ -132,4 +108,108 @@ object Crunchyroll {
|
||||
println("keyPairID: $keyPairID")
|
||||
}
|
||||
|
||||
|
||||
// TODO locale de-DE, categories
|
||||
/**
|
||||
* Browse the media available on crunchyroll.
|
||||
*
|
||||
* @param sortBy
|
||||
* @param n Number of items to return, defaults to 10
|
||||
*
|
||||
* @return A **[BrowseResult]** object is returned.
|
||||
*/
|
||||
suspend fun browse(sortBy: SortBy = SortBy.ALPHABETICAL, n: Int = 10): BrowseResult {
|
||||
val browseEndpoint = "/content/v1/browse"
|
||||
val parameters = listOf("sort_by" to sortBy.str, "n" to n)
|
||||
|
||||
val result = request(browseEndpoint, parameters)
|
||||
val browseResult = result.component1()?.obj()?.let {
|
||||
json.decodeFromString(it.toString())
|
||||
} ?: NoneBrowseResult
|
||||
|
||||
// add results to cache TODO improve
|
||||
browsingCache.clear()
|
||||
browsingCache.addAll(browseResult.items)
|
||||
|
||||
return browseResult
|
||||
}
|
||||
|
||||
// // TODO locale de-DE, type
|
||||
suspend fun search(query: String, n: Int = 10) {
|
||||
val searchEndpoint = "/content/v1/search"
|
||||
val parameters = listOf("q" to query, "n" to n)
|
||||
|
||||
val result = request(searchEndpoint, parameters)
|
||||
println("${result.component1()?.obj()?.get("total")}")
|
||||
|
||||
val test = json.decodeFromString<BrowseResult>(result.component1()?.obj()?.toString()!!)
|
||||
println(test.items.size)
|
||||
|
||||
// TODO return
|
||||
}
|
||||
|
||||
/**
|
||||
* series id == crunchyroll id?
|
||||
*/
|
||||
suspend fun series(seriesId: String): Series {
|
||||
val seriesEndpoint = "/cms/v2/$country/M3/crunchyroll/series/$seriesId"
|
||||
val parameters = listOf(
|
||||
"locale" to locale,
|
||||
"Signature" to signature,
|
||||
"Policy" to policy,
|
||||
"Key-Pair-Id" to keyPairID
|
||||
)
|
||||
|
||||
val result = request(seriesEndpoint, parameters)
|
||||
|
||||
return result.component1()?.obj()?.let {
|
||||
json.decodeFromString(it.toString())
|
||||
} ?: NoneSeries
|
||||
}
|
||||
|
||||
suspend fun seasons(seriesId: String): Seasons {
|
||||
val episodesEndpoint = "/cms/v2/$country/M3/crunchyroll/seasons"
|
||||
val parameters = listOf(
|
||||
"series_id" to seriesId,
|
||||
"locale" to locale,
|
||||
"Signature" to signature,
|
||||
"Policy" to policy,
|
||||
"Key-Pair-Id" to keyPairID
|
||||
)
|
||||
|
||||
val result = request(episodesEndpoint, parameters)
|
||||
|
||||
return result.component1()?.obj()?.let {
|
||||
println(it)
|
||||
json.decodeFromString(it.toString())
|
||||
} ?: NoneSeasons
|
||||
}
|
||||
|
||||
suspend fun episodes(seasonId: String): Episodes {
|
||||
val episodesEndpoint = "/cms/v2/$country/M3/crunchyroll/episodes"
|
||||
val parameters = listOf(
|
||||
"season_id" to seasonId,
|
||||
"locale" to locale,
|
||||
"Signature" to signature,
|
||||
"Policy" to policy,
|
||||
"Key-Pair-Id" to keyPairID
|
||||
)
|
||||
|
||||
val result = request(episodesEndpoint, parameters)
|
||||
|
||||
return result.component1()?.obj()?.let {
|
||||
println(it)
|
||||
json.decodeFromString(it.toString())
|
||||
} ?: NoneEpisodes
|
||||
}
|
||||
|
||||
suspend fun playback(url: String): Playback {
|
||||
val result = request("", url = url)
|
||||
|
||||
return result.component1()?.obj()?.let {
|
||||
println(it)
|
||||
json.decodeFromString(it.toString())
|
||||
} ?: NonePlayback
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package org.mosad.teapod.parser.crunchyroll
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
@ -26,9 +27,141 @@ data class Item(
|
||||
// TODO metadata etc.
|
||||
)
|
||||
|
||||
val NoneItem = Item("", "", "", "", "", Images(listOf(), listOf()))
|
||||
val NoneBrowseResult = BrowseResult(0, listOf())
|
||||
|
||||
@Serializable
|
||||
data class Images(val poster_tall: List<List<Poster>>, val poster_wide: List<List<Poster>>)
|
||||
// crunchyroll why?
|
||||
|
||||
@Serializable
|
||||
data class Poster(val height: Int, val width: Int, val source: String, val type: String)
|
||||
data class Poster(val height: Int, val width: Int, val source: String, val type: String)
|
||||
|
||||
/**
|
||||
* Series return type
|
||||
*/
|
||||
@Serializable
|
||||
data class Series(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val images: Images
|
||||
)
|
||||
val NoneSeries = Series("", "", "", Images(listOf(), listOf()))
|
||||
|
||||
|
||||
/**
|
||||
* Seasons data type
|
||||
*/
|
||||
@Serializable
|
||||
data class Seasons(val total: Int, val items: List<Season>)
|
||||
|
||||
@Serializable
|
||||
data class Season(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val series_id: String,
|
||||
val season_number: Int
|
||||
)
|
||||
|
||||
val NoneSeasons = Seasons(0, listOf())
|
||||
|
||||
/**
|
||||
* Episodes data type
|
||||
*/
|
||||
@Serializable
|
||||
data class Episodes(val total: Int, val items: List<Episode>)
|
||||
|
||||
@Serializable
|
||||
data class Episode(
|
||||
@SerialName("id") val id: String,
|
||||
@SerialName("title") val title: String,
|
||||
@SerialName("series_id") val seriesId: String,
|
||||
@SerialName("season_title") val seasonTitle: String,
|
||||
@SerialName("season_id") val seasonId: String,
|
||||
@SerialName("season_number") val seasonNumber: Int,
|
||||
@SerialName("episode") val episode: String,
|
||||
@SerialName("episode_number") val episodeNumber: Int,
|
||||
@SerialName("description") val description: String,
|
||||
@SerialName("next_episode_id") val nextEpisodeId: String = "", // use default value since the field is optional
|
||||
@SerialName("next_episode_title") val nextEpisodeTitle: String = "", // use default value since the field is optional
|
||||
@SerialName("is_subbed") val isSubbed: Boolean,
|
||||
@SerialName("is_dubbed") val isDubbed: Boolean,
|
||||
@SerialName("images") val images: Thumbnail,
|
||||
@SerialName("duration_ms") val durationMs: Int,
|
||||
@SerialName("playback") val playback: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Thumbnail(
|
||||
@SerialName("thumbnail") val thumbnail: List<List<Poster>>
|
||||
)
|
||||
|
||||
val NoneEpisodes = Episodes(0, listOf())
|
||||
val NoneEpisode = Episode(
|
||||
id = "",
|
||||
title = "",
|
||||
seriesId = "",
|
||||
seasonId = "",
|
||||
seasonTitle = "",
|
||||
seasonNumber = 0,
|
||||
episode = "",
|
||||
episodeNumber = 0,
|
||||
description = "",
|
||||
nextEpisodeId = "",
|
||||
nextEpisodeTitle = "",
|
||||
isSubbed = false,
|
||||
isDubbed = false,
|
||||
images = Thumbnail(listOf()),
|
||||
durationMs = 0,
|
||||
playback = ""
|
||||
)
|
||||
|
||||
/**
|
||||
* Playback/stream data type
|
||||
*/
|
||||
@Serializable
|
||||
data class Playback(
|
||||
@SerialName("audio_locale") val audioLocale: String,
|
||||
@SerialName("subtitles") val subtitles: Map<String, Subtitle>,
|
||||
@SerialName("streams") val streams: Streams,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Subtitle(
|
||||
@SerialName("locale") val locale: String,
|
||||
@SerialName("url") val url: String,
|
||||
@SerialName("format") val format: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Streams(
|
||||
@SerialName("adaptive_dash") val adaptive_dash: Map<String, Stream>,
|
||||
@SerialName("adaptive_hls") val adaptive_hls: Map<String, Stream>,
|
||||
@SerialName("download_hls") val download_hls: Map<String, Stream>,
|
||||
@SerialName("drm_adaptive_dash") val drm_adaptive_dash: Map<String, Stream>,
|
||||
@SerialName("drm_adaptive_hls") val drm_adaptive_hls: Map<String, Stream>,
|
||||
@SerialName("drm_download_hls") val drm_download_hls: Map<String, Stream>,
|
||||
@SerialName("trailer_dash") val trailer_dash: Map<String, Stream>,
|
||||
@SerialName("trailer_hls") val trailer_hls: Map<String, Stream>,
|
||||
@SerialName("vo_adaptive_dash") val vo_adaptive_dash: Map<String, Stream>,
|
||||
@SerialName("vo_adaptive_hls") val vo_adaptive_hls: Map<String, Stream>,
|
||||
@SerialName("vo_drm_adaptive_dash") val vo_drm_adaptive_dash: Map<String, Stream>,
|
||||
@SerialName("vo_drm_adaptive_hls") val vo_drm_adaptive_hls: Map<String, Stream>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Stream(
|
||||
@SerialName("hardsub_locale") val hardsubLocale: String,
|
||||
@SerialName("url") val url: String,
|
||||
@SerialName("vcodec") val vcodec: String,
|
||||
)
|
||||
|
||||
val NonePlayback = Playback(
|
||||
"",
|
||||
mapOf(),
|
||||
Streams(
|
||||
mapOf(), mapOf(), mapOf(), mapOf(), mapOf(), mapOf(),
|
||||
mapOf(), mapOf(), mapOf(), mapOf(), mapOf(), mapOf(),
|
||||
)
|
||||
)
|
||||
|
Reference in New Issue
Block a user