Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.discovr.media/llms.txt

Use this file to discover all available pages before exploring further.

When you build a streaming app detail page, you pull catalog info (synopsis, images, release date) from getMedia(). But that’s only half the story. You also need to know: Has this user already watched it? Did they like it? Can they resume? That’s what getMediaProfileMeta() does. It’s the viewer’s relationship with a specific title—what list it’s on, how far they got, whether they’ve seen it before, and optionally where to watch it. Think of it as layering user state on top of catalog facts. Your title sheet merges:
  • Catalog (via getMedia) — synopsis, images, cast, runtime
  • User state (via getMediaProfileMeta) — liked/watchlist status, resume position, watch history
Together, they tell you which button to show (“Resume from 45%”, “Add to List”, “Play”) and what metadata to highlight.
This endpoint requires an active profile session (start with selectProfile()). The profile is determined automatically from your session, so you only need to pass the mediaId. See Sessions, Profiles & Sign Out for setup. Code samples use TypeScript / Kotlin / Swift.

Four pieces of user state

getMediaProfileMeta() gives you four signals about how the viewer relates to this title: List membershipliked, disliked, super_liked, watchlist are booleans. true means the user has added it to that list. Use these to show which buttons are “active” (full heart, solid thumbs up). Playback — Is the user in the middle of watching? The playback object tells you whether they can resume and, if so, how far they got. For movies, it’s a simple percentage (0–100%). For TV, you also get the season and episode. This is what powers your “Resume from 45%” button. History — A simple true or false that says whether the user has watched this title before. Use it for “Previously watched” badges, not as a substitute for playback position. Providers (optional) — If you pass providers: true in your request, you’ll also get where-to-watch info (logos, links to streaming services). Skip this if your UI doesn’t need it—it costs extra on the backend.

How playback decides your button

The playback object has a simple contract: check inlist first. If it’s false, there’s nothing to resume—show “Play”. If it’s true, check whether data is present to decide between “Resume at X%” and “Play”.
playback.inlistHas data?What it meansYour button
falseNot watching this”Play”
truePartially watched”Resume at X%” (show progress bar)
trueStarted but not fully hydrated”Play” (fallback)
For TV shows, data also includes season and episode so you can show “Resume Season 2, Episode 5” instead of just a percentage. For movies, it’s just a progress percentage. Pro tip: Always check inlist before using data. A missing or stale data object with inlist: true is rare but possible—your fallback button should be “Play”.
Resume bars trace playback; “Seen before” badges trace history. They drift by design—for example something can sit in history while playback.inlist is false after removal or completion semantics.
List booleans (liked, disliked, …) remain orthogonal unless your client enforces mutual exclusivity in UX before /profile writes.

Building your detail page

Fetching in parallel keeps your title sheet fast. When the user taps a title card:
  1. Fetch getMedia() for catalog (synopsis, images, cast)
  2. Fetch getMediaProfileMeta() for user state (resume position, lists)
  3. Merge them into your view model
Once loaded, your UI logic is straightforward:
  • Show your primary button based on playback.inlist and playback.data (Resume vs Play)
  • Wire your like/dislike/watchlist buttons to the booleans
  • Show a “Previously watched” badge if history is true
After user actions — If the user adds/removes from a list, or watches the content, fetch getMediaProfileMeta() again to keep your UI in sync. For changes during playback, see Tracking Playback with Scrobbling. When switching profiles — If your app supports multiple profiles, call selectProfile() to mint a new session JWT for the new profile, then refetch getMediaProfileMeta(). The catalog can stay cached; only the user state changes.

Example: Resume vs Play plus liked

The snippet shows playback.inlist gating Resume versus Play, alongside a trivial liked read.
import type { MediaProfileMeta } from "discovr";

function primaryCta(meta: MediaProfileMeta): { label: string; progress?: number } {
	if (meta.playback.inlist && meta.playback.data) {
		const d = meta.playback.data;
		return { label: "Resume", progress: d.progress };
	}
	return { label: "Play" };
}

function liked(meta: MediaProfileMeta): boolean {
	return meta.liked;
}
Back: Profile context