diff --git a/firetv/emulator-test.sh b/firetv/emulator-test.sh index 50ee5c1..8e76e6f 100644 --- a/firetv/emulator-test.sh +++ b/firetv/emulator-test.sh @@ -1,6 +1,12 @@ #!/bin/bash # Boots an Android TV emulator (API 30, the base of Fire OS 8), installs the APK, # drives it with remote-control keys and saves 1920x1080 screenshots to store/shots. +# +# THIS IS A FUNCTIONAL TEST, NOT THE SOURCE OF STORE ART. It searches the live +# directory, so 04-results, 05-results-nav, 05b-results-right and 08-menu-search +# are full of other people's cover art and show names. Uploading those is what +# got 1.0.0 rejected on 2026-11-04 under Amazon's IP and Copyright policy. +# Store screenshots come from store/capture.sh (/tv-preview), never from here. # Runs inside gradle:8.7-jdk17 with --device /dev/kvm and the SDK cache at /sdk: # docker run --rm --device /dev/kvm -v ~/scratch/android-sdk:/sdk -v "$PWD/firetv:/w" -w /w \ # gradle:8.7-jdk17 bash emulator-test.sh diff --git a/firetv/store/capture.sh b/firetv/store/capture.sh new file mode 100755 index 0000000..5aabf5e --- /dev/null +++ b/firetv/store/capture.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# Store screenshots for the Amazon Appstore listing, 1920x1080. +# +# These must carry NO third-party cover art and NO real show names. Amazon +# rejected 1.0.0 on 2026-11-04 because the screenshots were live captures of +# /search: real artwork and real titles (Mighty Oaks Daily Devotional, The +# Harry Sisson Show) were in the listing, and we hold no licence to any of it. +# +# So the grid comes from /tv-preview, which renders the real card grid with +# invented shows and no image URLs — then draws its own letter tile for +# every square. The app is a WebView of exactly this site at exactly this size, +# so a headless capture is what the TV shows, without needing the Android SDK. +# +# Usage (defaults to production): +# sh firetv/store/capture.sh [BASE_URL] +set -eu +BASE=${1:-https://p0dcasters.com} +OUT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)/shots +mkdir -p "$OUT" + +shot() { # shot + docker run --rm --network host -v "$OUT:/out" -w /out \ + --entrypoint chromium-browser zenika/alpine-chrome:124 \ + --headless --no-sandbox --disable-gpu --hide-scrollbars \ + --window-size=1920,1080 --virtual-time-budget=12000 \ + --screenshot="/out/$1.png" "$BASE$2" >/dev/null 2>&1 + echo "$1.png <- $BASE$2" +} + +shot 01-home / +shot 02-grid /tv-preview + +echo +echo "Check both by eye before uploading: no cover art that is not ours, no real show titles." +ls -l "$OUT"/01-home.png "$OUT"/02-grid.png diff --git a/firetv/store/listing.md b/firetv/store/listing.md index b6cce52..d9a671f 100644 --- a/firetv/store/listing.md +++ b/firetv/store/listing.md @@ -16,7 +16,9 @@ Every podcast with a feed, plus live radio and TV. Free, no account. podcasters puts p0dcasters.com on your TV: every podcast with a public feed, on any host, plus live radio and TV channels. -Over 390,000 live shows read straight from their RSS feeds: shows on their own domain, shows on Anchor, Buzzsprout, Libsyn and every other host, and the big libraries like TWiT, NPR and the BBC. Free, no account, nothing to sign up for. +Over 390,000 live shows read straight from their RSS feeds: shows on their own domain, shows on every hosting platform, and the big network libraries. Free, no account, nothing to sign up for. + +podcasters is a podcast reader. It lists publicly published RSS feeds and plays them from the publisher's own server. It does not host, rehost or alter anyone's audio, and every show remains the property of whoever publishes it. Publishers can have a feed removed at any time from the contact page. Made for the remote: - Press Menu to search, and type with the Fire TV keyboard or your voice. @@ -47,4 +49,18 @@ podcasts, podcast player, radio, live tv, p0dcasters, podcasters, rss, free podc | Icon 512x512 / 114x114 | out/icon-512.png, out/icon-114.png | | Fire TV icon 1280x720 | out/firetv-icon-1280x720.png | | Fire TV background 1920x1080 | out/firetv-background-1920x1080.png | -| Screenshots 1920x1080 | shots/01-home.png, 05-results-nav.png, 04-results.png, 08-menu-search.png | +| Screenshots 1920x1080 | shots/01-home.png, 02-grid.png, 03-keyboard.png | + +### Screenshots must not carry anyone else's artwork + +Amazon rejected version 1.0.0 on 2026-11-04 under the Intellectual Property +and Copyright policy. The screenshots were live captures of `/search`, so real +cover art and real show names — Mighty Oaks Daily Devotional, The Harry Sisson +Show and others — were sitting in the store listing. We have no licence to +anyone's cover art; a feed being public does not grant one. + +Capture the grid from **`/tv-preview`**, never from `/search`, `/podcasts` or +the home page's "deep catalogues" row. That page renders the real card grid +with invented shows and no image URLs, so `` draws its own typographic +tile for every square. The search screenshot must be taken with the keyboard +open and **no results behind it**. diff --git a/firetv/store/shots/01-home.png b/firetv/store/shots/01-home.png deleted file mode 100644 index 2387b39..0000000 Binary files a/firetv/store/shots/01-home.png and /dev/null differ diff --git a/firetv/store/shots/03-keyboard.png b/firetv/store/shots/03-keyboard.png deleted file mode 100644 index 2387b39..0000000 Binary files a/firetv/store/shots/03-keyboard.png and /dev/null differ diff --git a/firetv/store/shots/04-results.png b/firetv/store/shots/04-results.png deleted file mode 100644 index ecc1e4b..0000000 Binary files a/firetv/store/shots/04-results.png and /dev/null differ diff --git a/firetv/store/shots/05-results-nav.png b/firetv/store/shots/05-results-nav.png deleted file mode 100644 index 3cd286d..0000000 Binary files a/firetv/store/shots/05-results-nav.png and /dev/null differ diff --git a/firetv/store/shots/05b-results-right.png b/firetv/store/shots/05b-results-right.png deleted file mode 100644 index 5453af9..0000000 Binary files a/firetv/store/shots/05b-results-right.png and /dev/null differ diff --git a/firetv/store/shots/06-back.png b/firetv/store/shots/06-back.png deleted file mode 100644 index 4b8ea4f..0000000 Binary files a/firetv/store/shots/06-back.png and /dev/null differ diff --git a/firetv/store/shots/07-menu-search.png b/firetv/store/shots/07-menu-search.png deleted file mode 100644 index 86ac169..0000000 Binary files a/firetv/store/shots/07-menu-search.png and /dev/null differ diff --git a/firetv/store/shots/08-menu-search.png b/firetv/store/shots/08-menu-search.png deleted file mode 100644 index 5dc70b8..0000000 Binary files a/firetv/store/shots/08-menu-search.png and /dev/null differ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index daf1a70..1182edb 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -18,7 +18,7 @@ export const metadata: Metadata = { // 146 characters. Kept short: a wording of 176 once lost its last clause to // truncation in exactly the snippet it was written for. description: - "A free directory of every live podcast with an RSS feed — on any host, plus networks like TWiT, NPR and the BBC. Listen in the browser.", + "A free directory of every live podcast with an RSS feed — on any host, plus the big network libraries. Listen in the browser.", openGraph: { type: "website", siteName: "p0dcasters", diff --git a/src/app/opengraph-image.tsx b/src/app/opengraph-image.tsx index a907135..4599d3a 100644 --- a/src/app/opengraph-image.tsx +++ b/src/app/opengraph-image.tsx @@ -101,7 +101,7 @@ export default async function Image() { marginTop: 22, }} > - Self-hosted shows, every hosting platform, and networks like TWiT, NPR and the BBC. + Self-hosted shows, every hosting platform, and the big network libraries. Listen in the browser and follow your favourites. diff --git a/src/app/page.tsx b/src/app/page.tsx index 2c38771..0c9f740 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -45,10 +45,9 @@ export default async function Home() {

Every podcast with a feed — on any host.

{total.toLocaleString()} live shows, read from - their RSS feeds: shows on their own domain, shows on Anchor, Buzzsprout, Libsyn - and every other host, and the big libraries — TWiT, NPR, the BBC and the rest of - the networks. Free, no account, listen in the - browser. Only the shows on their own domain?{" "} + their RSS feeds: shows on their own domain, shows on every hosting platform, + and the big library networks. Free, no account, + listen in the browser. Only the shows on their own domain?{" "} Self-hosted. Missing one?{" "} Add it.

diff --git a/src/app/tv-preview/page.tsx b/src/app/tv-preview/page.tsx new file mode 100644 index 0000000..4ae45e0 --- /dev/null +++ b/src/app/tv-preview/page.tsx @@ -0,0 +1,79 @@ +import type { Metadata } from "next"; +import type { Podcast } from "@/lib/db"; +import Card from "@/components/Card"; + +/** + * Store-capture page. Renders the real grid with invented shows and no + * artwork, so a screenshot carries none of the cover art or show names that + * belong to the people who publish the feeds we list. + * + * It exists because the Amazon Appstore rejected com.p0dcasters.tv on + * 2026-11-04: the screenshots were live captures of /search, so real third-party + * cover art and titles sat in the store listing, which is exactly what their IP + * policy forbids. Capture Fire TV screenshots HERE, never from /search. + * + * `image_url: ""` is the point — falls back to its own typographic tile, + * so every square is drawn by us. Keep the names invented, and keep them + * obviously generic so nobody reads them as a real show. + */ +export const metadata: Metadata = { + title: "Preview", + robots: { index: false, follow: false }, +}; + +const SAMPLE: Array & { days: number }> = [ + { title: "The Sample Show", host: "example.com", episode_count: 412, days: 1 }, + { title: "Morning Build Notes", host: "example.org", episode_count: 196, days: 2 }, + { title: "Field Recordings Weekly", host: "example.net", episode_count: 88, days: 1 }, + { title: "Two Founders Talking", host: "example.com", episode_count: 523, days: 3 }, + { title: "The Long Commute", host: "example.org", episode_count: 274, days: 1 }, + { title: "Kitchen Table History", host: "example.net", episode_count: 331, days: 4 }, + { title: "Nightshift Radio Hour", host: "example.com", episode_count: 605, days: 2 }, + { title: "Questions for Later", host: "example.org", episode_count: 147, days: 1 }, + { title: "A Walk and a Microphone", host: "example.net", episode_count: 239, days: 5 }, + { title: "Slow News Sunday", host: "example.com", episode_count: 164, days: 2 }, + { title: "The Back Catalogue", host: "example.org", episode_count: 718, days: 1 }, + { title: "Notes from the Workshop", host: "example.net", episode_count: 205, days: 3 }, +]; + +const DAY = 86_400; + +export default function TvPreviewPage() { + const now = Math.floor(Date.now() / 1000); + const shows = SAMPLE.map((s, i) => ({ + id: i + 1, + slug: `sample-${i + 1}`, + guid: null, + feed_url: "", + title: s.title, + description: "", + // Empty on purpose: draws our own letter tile instead. + image_url: "", + link: null, + host: s.host, + author: null, + owner: null, + explicit: 0, + language: "en", + lang_base: "en", + category: null, + categories: null, + episode_count: s.episode_count, + newest_pubdate: now - s.days * DAY, + oldest_pubdate: null, + created_on: null, + })) as unknown as Podcast[]; + + return ( +
+
+

Browse every show

+
+ {shows.map((p) => ( + + ))} +
+
+
+ ); +}