Portfolio demo Pick a role How it's built
Canopy

Portfolio demo

How it's built

Canopy is one Flask app on Google Cloud Run, backed by Firestore. It started life as a per-minute streaming product for another brand; this demo re-themes it around the Mindo cloud forest without changing how it works underneath.

One app, three video sources

All three play in the browser; only Cloudflare's WebRTC handshake goes through the app. Dashed: the fallback order. Each source can be switched off without losing its settings.

Listener's browser / PWA
player, heartbeat every 10 s
Admin consoles
platform + per-tenant
Flask on Cloud Run
three sign-in doors per org
per-second billing
same-origin WHEP proxy
per-org branding + PWA
Firestore
users, ledger, sessions
Stripe (test mode)
Checkout + webhook
Video sources, in fallback order
1
Cloudflare Stream
WebRTC (WHEP), signed
if not live
2
Dailymotion
24/7 embed
if down
3
Backup video
looped MP4 in Cloud Storage

Where each piece lives

01

Billing by the second

The player sends a heartbeat every 10 seconds. The server charges the time actually elapsed since the last charge (capped at 30 seconds, so a stalled tab can't drain a balance), writes a consumption entry to the ledger and awards a feather per full minute.

app/routes/consumer.py · heartbeatapp/services/firestore_db.py · apply_light_quotient
The forest video
Live
  1. 1 Cloudflare Stream · WebRTC, signed Playing
  2. 2 Dailymotion · 24/7 embed Standby
  3. 3 Backup video · looped MP4 Standby
02

Live video with fallback

The browser posts its WebRTC offer to the app, which signs the Cloudflare WHEP URL and forwards it, so no Cloudflare credentials reach the page. If Cloudflare isn't live the player moves to Dailymotion, then to a looped backup video.

app/routes/consumer.py · stream_whepapp/services/delivery_sources.pyapp/static/js/watch.js

And the rest

03

Payments

Stripe Checkout in test mode. Minutes are credited from the success page and again by the webhook; a fulfilment record per session makes the second one a no-op.

app/services/stripe_billing.pyapp/routes/stripe_webhook.py
04

Multi-tenant white label

Every organization lives under its own /<slug> with separate sign-in doors for listeners and admins, its own colours and logo, and its own installable app (manifest and icon generated per org). Nubes Lodge is the demo tenant.

app/routes/org.pyapp/services/watch_branding.pyapp/services/pwa.py
05

Sign-in

Google OAuth or a 15-minute emailed link, both resolved by the same function so they can't disagree about who may sign in where. The demo role buttons set the same session.

app/routes/auth.pyapp/services/magic_link.pyapp/services/demo.py
06

Brand in one place

Name, colours, fonts, logos and the points name come from one module, so renaming the product is a one-file change.

app/brand.pyscripts/make_brand_art.py
07

Deploys

A manually triggered GitHub Actions workflow authenticates to Google Cloud with Workload Identity Federation (no stored keys), builds the container in Cloud Build and deploys it to Cloud Run.

.github/workflows/deploy.ymlcloudbuild.yaml

See it running

Pick a role and walk straight in, with a month of sample data already there.

Try the demo
Canopy

A portfolio demo. Until cameras go up in Mindo the video is looped stock footage of a misty forest (not Mindo itself), and payments run in Stripe's test mode. Bird recordings by Niels Krabbe via xeno-canto, CC BY-SA 4.0.

Built by Matthew Massaro