Skip to content

Repository files navigation

nullpod

Ad operations for video podcasts. Nullpod combines episode ingest, sponsor creative, dynamic marker placement, waveform navigation, composed playback, and timeline snapshots in one dark control surface.

Stack

  • Next.js 16 and React 19
  • TypeScript and Tailwind CSS
  • Drizzle ORM with Cloudflare D1
  • Cloudflare Stream for video ingest and playback
  • Cloudflare R2 for media storage
  • Cloudflare Media Transformations for audio extraction
  • OpenNext adapter for Workers deployment
  • TanStack Query and Zustand for editor state
  • HLS.js for browser playback

Local Development

npm install
npm run dev

Open http://localhost:3000. The root route redirects to the episode launcher at /ads.

The dev server uses initOpenNextCloudflareForDev() to provide local D1, R2, and Media bindings via getPlatformProxy.

Cloudflare Stream

Runtime uploads and Stream synchronization use:

CLOUDFLARE_ACCOUNT_ID=<your-account-id>
CLOUDFLARE_STREAM_API_TOKEN=<your-stream-token>

Useful commands:

npm run cf:whoami
npm run cf:stream:list -- --limit 20
npm run cf:stream:get -- --uid <stream-uid>
npm run cf:stream:seed -- --map scripts/cloudflare/stream-seed-map.json

Deploy to Cloudflare Workers

# Apply D1 migrations
npx wrangler d1 migrations apply nullpod --remote

# Build and deploy
npm run deploy

Preview locally in the Workers runtime:

npm run preview

Generate Cloudflare env types:

npm run cf-typegen

Bindings

Binding Type Purpose
DB D1 Relational data (episodes, markers, sessions, snapshots)
MEDIA R2 Media artifacts and seed images
MEDIA_TRANSFORM Media Audio extraction for waveform analysis
ASSETS Assets Static assets from Next.js build

Verification

npm run lint
npm run build

Architecture Notes

  • D1 data store replaces better-sqlite3. All queries use drizzle-orm/d1 via getCloudflareContext().
  • Synthetic waveforms are generated from duration data. Real PCM analysis requires a Cloudflare Container with FFmpeg.
  • Export jobs queue but do not execute on Workers. Stitched export execution requires a Cloudflare Container.
  • Seed data is inserted on first D1 query via ensureD1Seeded().
  • Seed images are served from public/seed/ as static assets.

About

Ad operations for video podcasts.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages