Implementation
Fetching metadata#
This guide shows how to fetch detailed metadata using the capability-first MetadataProviderApi.
Overview#
To support metadata, expose a MetadataProviderApi from your ProviderPlugin:
class TestProviderPlugin : ProviderPlugin() {
private val client by lazy { OkHttpClient() }
private val metadataApi by lazy {
TestMetadataApi(
client = client,
tmdbApiKey = settings.getString("tmdb_api_key", null) ?: "",
)
}
override fun getMetadataApi(context: Context): MetadataProviderApi = metadataApi
}MetadataProviderApi defines a single method:
interface MetadataProviderApi {
suspend fun getMetadata(film: Film): FilmMetadata
}The film parameter is usually a lightweight item (often a FilmSearchItem). Your job is to fetch a richer FilmMetadata object (Movie or TvShow).
FilmDetails still exists, but it’s deprecated in favor of FilmMetadata.
Example using TMDB Details API#
In this section, TMDB’s /movie/{id} and /tv/{id} endpoints are used as an example.
Define DTO classes#
Use Kotlinx Serialization DTOs and map them to Core Stubs models.
@Serializable
data class TmdbMovieDto(
val id: Int,
val title: String,
@SerialName("poster_path") val posterPath: String? = null,
@SerialName("backdrop_path") val backdropPath: String? = null,
val homepage: String? = null,
val overview: String? = null,
val adult: Boolean = false,
@SerialName("original_language") val originalLanguage: String? = null,
@SerialName("release_date") val releaseDate: String? = null,
@SerialName("vote_average") val voteAverage: Double? = null,
val tagline: String? = null,
) {
fun toMovie(providerId: String): Movie {
val poster = posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }
val backdrop = backdropPath?.let { "https://image.tmdb.org/t/p/w780$it" }
return Movie(
id = id.toString(),
title = title,
posterImage = poster,
homePage = homepage,
backdropImage = backdrop,
externalIds = mapOf(FilmIdSource.TMDB to id.toString()),
language = originalLanguage,
releaseDate = parseDate(releaseDate),
rating = voteAverage,
providerId = providerId,
adult = adult,
overview = overview,
tagLine = tagline,
)
}
}
@Serializable
data class TmdbTvShowDto(
val id: Int,
@SerialName("name") val title: String,
@SerialName("poster_path") val posterPath: String? = null,
@SerialName("backdrop_path") val backdropPath: String? = null,
val homepage: String? = null,
val overview: String? = null,
val adult: Boolean = false,
@SerialName("original_language") val originalLanguage: String? = null,
@SerialName("first_air_date") val firstAirDate: String? = null,
@SerialName("vote_average") val voteAverage: Double? = null,
val tagline: String? = null,
@SerialName("number_of_episodes") val numberOfEpisodes: Int = 0,
@SerialName("number_of_seasons") val numberOfSeasons: Int = 0,
) {
fun toTvShow(providerId: String): TvShow {
val poster = posterPath?.let { "https://image.tmdb.org/t/p/w500$it" }
val backdrop = backdropPath?.let { "https://image.tmdb.org/t/p/w780$it" }
return TvShow(
id = id.toString(),
title = title,
posterImage = poster,
homePage = homepage,
backdropImage = backdrop,
externalIds = mapOf(FilmIdSource.TMDB to id.toString()),
language = originalLanguage,
releaseDate = parseDate(firstAirDate),
rating = voteAverage,
providerId = providerId,
adult = adult,
overview = overview,
tagLine = tagline,
totalEpisodes = numberOfEpisodes,
totalSeasons = numberOfSeasons,
)
}
}Implement getMetadata(...)#
Choose the correct TMDB endpoint based on film.filmType, request, parse, then map.
private fun buildTmdbDetailsUrl(
base: String,
id: String,
apiKey: String,
): String = "$base/$id?api_key=$apiKey"
override suspend fun getMetadata(film: Film): FilmMetadata {
val providerId = film.providerId
val tmdbId = film.externalIds[FilmIdSource.TMDB] ?: film.id
val endpoint = when (film.filmType) {
FilmType.MOVIE -> "https://api.themoviedb.org/3/movie"
FilmType.TV_SHOW -> "https://api.themoviedb.org/3/tv"
}
val response = client
.request(url = buildTmdbDetailsUrl(endpoint, tmdbId, tmdbApiKey))
.execute()
return when (film.filmType) {
FilmType.MOVIE -> response.fromJson<TmdbMovieDto>().toMovie(providerId)
FilmType.TV_SHOW -> response.fromJson<TmdbTvShowDto>().toTvShow(providerId)
}
}Final output#
class TestMetadataApi(
private val client: OkHttpClient,
private val tmdbApiKey: String,
) : MetadataProviderApi {
private fun buildTmdbDetailsUrl(
base: String,
id: String,
apiKey: String,
): String = "$base/$id?api_key=$apiKey"
override suspend fun getMetadata(film: Film): FilmMetadata {
val providerId = film.providerId
val tmdbId = film.externalIds[FilmIdSource.TMDB] ?: film.id
val endpoint = when (film.filmType) {
FilmType.MOVIE -> "https://api.themoviedb.org/3/movie"
FilmType.TV_SHOW -> "https://api.themoviedb.org/3/tv"
}
val response = client
.request(url = buildTmdbDetailsUrl(endpoint, tmdbId, tmdbApiKey))
.execute()
return when (film.filmType) {
FilmType.MOVIE -> response.fromJson<TmdbMovieDto>().toMovie(providerId)
FilmType.TV_SHOW -> response.fromJson<TmdbTvShowDto>().toTvShow(providerId)
}
}
}