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)
        }
    }
}