npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-tv-recommended-content

v1.1.4

Published

Publish content to the Android TV / Google TV home screen from your React Native app to the "Continue Watching" (Watch Next) row, and your own fully custom recommendation channels

Readme

react-native-tv-recommended-content

Publish content to the Android TV / Google TV home screen from your React Native app to the "Continue Watching" (Watch Next) row, and your own fully custom recommendation channels.

npm version Typescript NPM Downloads license platform

Built directly on top of androidx.tvprovider (WatchNextProgram, PreviewProgram, and Channel), fully typed, and safe to call from any platform. It no-ops gracefully on iOS, tvOS, and non-TV Android instead of crashing.


📋 Table of Contents


🖥️ Platform Support

| Platform | Supported | Behavior | |-------------------------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Android TV / Google TV | ✅ | Fully functional | | Android (mobile/tablet) | 🚫 | All methods resolve to safe defaults (null or false or 0 or [] respectively). No-op, no crash. | | iOS / tvOS | 🚫 | Same safe no-op / no-crash behavior. There is no tvOS equivalent of Watch Next/Channels (tvOS has its own separate, unrelated "Top Shelf" API), so no native implementation exists for this package at at this time. But could exist in the future. |

Every method is safe to call on any platform. Unsupported platforms are resolved gracefully.


📦 Installation

npm install react-native-tv-recommended-content
# or
yarn add react-native-tv-recommended-content

No manual native linking or AndroidManifest.xml changes are required. Autolinking handles package registration, and Watch Next / Channels don't require any special Android permissions.


🔧 Quick Start

import TVRecommendedContent, { ProgramType } from 'react-native-tv-recommended-content';

// Add/update a movie in the Watch Next row (call this on pause/exit)
await TVRecommendedContent.addProgramToWatchNext({
  contentId: 'movie-123',
  title: 'The Great Adventure',
  posterUrl: 'https://example.com/poster.jpg',
  deepLinkUri: 'myapp://play/movie-123?resume=452000',
  playbackPosition: 452000, // 7m32s in, in milliseconds
  duration: 5400000,        // 90 minutes total
  type: ProgramType.MOVIE,
});

// Create a custom channel. The first one you ever create becomes your
// app's protected "default" channel automatically (see Core Concepts below)
const channelId = await TVRecommendedContent.createChannel({
  displayName: 'New Releases',
  appLinkUri: 'myapp://browse/new-releases',
});

// Add content into that channel
await TVRecommendedContent.addProgramToChannel(channelId, {
  contentId: 'movie-456',
  title: 'Another Great Film',
  posterUrl: 'https://example.com/poster2.jpg',
  deepLinkUri: 'myapp://play/movie-456',
  type: ProgramType.MOVIE,
});

You can also import individual functions instead of the default export:

import { getChannels, remove } from 'react-native-tv-recommended-content';

const channels = await getChannels();
const removedCount = await remove('movie-456'); // removes from everywhere at once

📚️ Core Concepts

  1. Watch Next is a single, system-managed row ("Continue Watching") shared across all apps. You don't create it, you just publish/remove entries from it.
  2. Channels are rows you create and fully control (e.g. "New Releases", "Because You Watched X"). Each channel can hold many programs.
  3. The default channel: the very first channel your app creates is automatically treated by the system as "browsable". It appears on the home screen immediately, with no user approval needed. Every channel after that requires the user to explicitly approve adding it (the system shows a permission prompt when you call createChannel). See more on "default channels" here. This package tracks which channel is yours to protect. The deleteChannel() api will refuse to delete the default channel unless you explicitly pass forceDeleteIfDefault: true, since a deleted default channel can't automatically reappear.
  4. contentId is your own stable unique identifier for a piece of content (e.g. your backend's movie/episode ID). Every method that looks up, updates, or removes a program does so by matching this field.

🔑️ API Reference

Watch Next

  • addProgramToWatchNext(programData: ProgramData):

    Publishes or updates an entry in the Watch Next row. If an entry with the same contentId already exists, it's updated in place (its progress and metadata refreshed) rather than duplicated. Resolves with the content provider URI string, or null on unsupported platforms.
  • removeWatchNext(contentId: string):

    Removes one entry from Watch Next specifically. Resolves true if something was found and removed, false otherwise.
  • clearWatchNext():

    Removes every Watch Next entry belonging to your app. Resolves with the count removed.

Channel Management

  • createChannel(data: CreateChannelData):

    Creates a new home screen channel. Resolves with the new channel's ID.
  • updateChannel(channelId: string, data: UpdateChannelData):

    Updates an existing channel. Only fields present in data are changed. Everything else is preserved as-is.
  • deleteChannel(options: DeleteChannelOptions):

    Deletes a channel and, per the platform's own behavior, its associated programs. Rejects with error code DEFAULT_CHANNEL_PROTECTED if; the target is your app's default channel, and forceDeleteIfDefault wasn't set to true.
  • getChannels():

    Lists every channel belonging to your app. Resolves with an array of found channels and their properties (TvChannel[])
  • getChannelsCount():

    Returns how many channels your app currently has.

Programs Within a Channel

  • addProgramToChannel(channelId: string, programData: ProgramData):

    Publishes or updates a program inside a specific channel (same update-in-place-by-contentId behavior as addProgramToWatchNext).
  • removeFromChannel(contentId: string, channelId: string):

    Removes one program from one specific channel.
  • clearChannel(channelId: string):

    Removes every program from a channel without deleting the channel itself. Resolves with the count removed.

Cross-cutting

APIs that can be used across both custom channels and the system-owned Watch Next channel.

  • remove(contentId: string):

    Removes every instance of a given contentId, across all your channels, including Watch Next. Useful for a single "delete this title everywhere" action (e.g. when content is taken down entirely). Resolves with the total count removed across all locations.
  • clearAll():

    Wipes everything (every program) your app has added across your own custom channels and Watch Next. This does not delete the channels themselves, only their contents. Resolves with the total count removed.

⚙️️ Types and Enums

All types are exported from the package root: (import { ProgramType, GenreType, ... } from 'react-native-tv-recommended-content').

| Type | Import Type | Description | |------------------------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ProgramType | ENUM | MOVIE, TV_EPISODE, TV_SERIES, CLIP | | ReviewRatingStyle | ENUM | STARS, THUMBS_UP_DOWN, PERCENTAGE | | GenreType | ENUM | Canonical genre tags (COMEDY, DRAMA, SPORTS, etc.) | | AspectRatio | ENUM | RATIO_16_9, RATIO_1_1, RATIO_2_3, RATIO_3_2, RATIO_3_4, RATIO_4_3, MOVIE_POSTER | | Availability | ENUM | AVAILABILITY_AVAILABLE, AVAILABILITY_FREE, AVAILABILITY_FREE_WITH_ADS, AVAILABILITY_FREE_WITH_SUBSCRIPTION, AVAILABILITY_PAID_CONTENT, AVAILABILITY_PURCHASED | | ContentRatingSystem | ENUM | Country-specific rating system identifiers (US_TV, US_MV, KR_TV, AU_TV, etc.) | | ContentRating | TYPE | { ratingSystem, rating, subRatings?, domain? } | | ProgramData | TYPE | Full metadata object accepted byaddProgramToWatchNext / addProgramToChannel - see inline JSDoc for every field, including TV-episode-specific (episodeTitle, seasonNumber, etc.) and clip/series-specific fields | | CreateChannelData | TYPE | { displayName, appLinkUri, description?, appLinkText?, appLinkColor?, appLinkIconUri?, appLinkPosterArtUri?, displayOrder?, searchable? } | | UpdateChannelData | TYPE | Partial<CreateChannelData> | | DeleteChannelOptions | TYPE | { channelId, forceDeleteIfDefault? } | | TvChannel | TYPE | Shape returned bygetChannels() |

🔔 Important field-naming note:

For type: ProgramType.TV_EPISODE, when adding a program to a channel or the "Watch Next" row, the title field must hold the series name (e.g. "Breaking Bad"), not the episode's own name. The episode's own name goes in episodeTitle. This mirrors Android's own underlying data model and is easy to get backwards; getting it wrong doesn't throw an error, it just mislabels the card on screen.


📜 Watch Next Quality Guidelines

These aren't rules this library enforces for you. They're Google's own published guidelines for what belongs in Watch Next, worth building your calling logic around:

  • Only add movies and TV episodes. Not clips, trailers, or short-form content.
  • "Started" thresholds: A movie counts as started after 3% or 2 minutes watched (whichever comes first); a TV episode after 2 minutes.
  • "Finished" means the end credits have started. Remove the entry at that point rather than leaving it stale. (An approximation like "under 3 minutes remaining" works if you don't have real credit-detection).
  • Keep at most one Watch Next entry per TV series at a time.
  • When an episode finishes, add the next episode in the series rather than just removing the finished one.
  • Visit the official guidelines page for more information and details.

🔧 Troubleshooting

  • "Unsupported class file major version NN" during a Gradle build. This is a JDK-vs-Gradle version mismatch, unrelated to this package. Your system's active JDK is newer than the project's Gradle version supports. Point Gradle at an older JDK (17 is the safe default) via org.gradle.java.home in android/gradle.properties, rather than changing your system-wide JAVA_HOME.

  • Cannot add extension with name 'kotlin' / a ClassCastException involving BaseExtension. Both are Android Gradle Plugin 9.0+ migration issues (its new built-in Kotlin support and new DSL implementation), not issues with this package. Add to your app's android/gradle.properties:

    android.builtInKotlin=false
    android.newDsl=false

    Note these are temporary opt-outs Google has said will be removed in a future AGP major version. Treat as a stopgap, not a permanent fix.

  • My Watch Next / channel entries aren't appearing on the home screen at all. These can be one of these independent causes:

    1. You're missing a required field. This package sets lastEngagementTimeUtcMillis for you automatically, but if entries still don't show, verify that posterUrl is a real, reachable URL.
    2. On genuine Google TV devices, the "Continue Watching" row specifically requires prior certification approval from Google, a server-side gate, separate from whether your code is correct. Plain AOSP Android TV (non-Google-TV-branded, leanback launcher) doesn't have this restriction. Custom channels you create yourself are not affected by this, only the system Watch Next row is.
    3. A newly created channel isn't showing up: Every channel after your app's first one requires the user to explicitly approve it. This package triggers that system permission prompt automatically inside createChannel, but the user still has to accept it. Only the very first channel your app ever creates is exempt from this. See here.

🧪 Testing

  1. Clone the repo and run yarn at the root.
  2. yarn example android runs the included example app.
  3. Test against a real Android TV device or the Android TV emulator where possible. Some behavior (notably the Google TV certification gate mentioned earlier) can only be observed on genuine Google TV hardware/launcher, not a plain AOSP Android TV emulator image.
  4. There's no meaningful unit-test surface on the JS side by design. This package is a thin, deliberately logic-free pass-through to native code; the real behavior to verify is always on-device.

🤝 Contributing

See CONTRIBUTING.md and please follow the Code of Conduct.


🔒 License

Apache-2.0 © MadeByRaymond (Daniel Obiekwe)


❤️ Support

If this package saved you from writing raw androidx.tvprovider Kotlin yourself, consider buying me a coffee:

Buy Me a Smoothie

Issues and feature requests: GitHub Issues