Skip to content

Demo data

approved

The versioned demo library: video documents, configuration shapes, and the dummy feed, with media served from a remote bucket.

Purpose

Two layers with distinct jobs: videos own the facts, configs own the shape, and the demo pages marry them. A developer copies a fully expanded configuration from the showcase and sees the player. The player itself never knows this folder exists; it only ever receives complete configurations.

Folder layout

text
data/
  README.md                 how this folder works, how to add a video or a config
  videos/
    index.json              ordered array of video ids
    <id>/video.json         one video document per id
  configs/
    index.json              ordered array of config ids
    <id>.json               one configuration shape per id
  feeds/
    shorts.json             the dummy feed: ordered portrait video ids

Video documents

The content library. Each document carries facts only: the config contract's content section plus the share URLs. It never contains layout, controls, capabilities, or strings decisions.

json
{
  "id": "quantify-genocide-impact",
  "content": {
    "title": "How can we quantify the immediate and lingering impact of genocide?",
    "author": "Diego Alburez-Gutierrez",
    "description": "A short synopsis used by share targets.",
    "poster": "https://demo.lt.org/videos/portrait/quantify-genocide-impact/poster.jpg",
    "duration": 609.4,
    "sources": [
      {
        "src": "https://demo.lt.org/videos/portrait/quantify-genocide-impact/video.mp4",
        "type": "video/mp4"
      }
    ],
    "tracks": [
      {
        "kind": "subtitles",
        "label": "English",
        "srclang": "en",
        "src": "https://demo.lt.org/videos/portrait/quantify-genocide-impact/en.vtt",
        "default": true
      }
    ],
    "chapters": [{ "name": "Question", "start": 9 }],
    "metadata": {
      "inANutshell": { "generatedByAi": true, "keyTakeaway": "…", "summary": "…" }
    }
  },
  "share": {
    "canonicalUrl": "https://lt.org/publication/example",
    "embedUrl": "https://lt.org/embed/example"
  }
}

Library composition:

  • Ten portrait videos, each carrying the full metadata set: every context panel view populated (in a nutshell, key concepts, research field, transcript, underlying publication, related videos), so each short demonstrates the panel completely, including its refresh across feed swipes.
  • Three landscape videos: one with chapters and two subtitle languages, one with the full metadata set, one minimal.

The library

Every video has a dedicated page with the playing video, its document, and copyable bucket links. Portrait titles and metadata are invented demo fiction over real media; chapters are the real ones. Captions and transcripts for portrait videos arrive together with the AI transcription pass.

Portrait

Landscape

Configuration shapes

Each file in configs/ is final-form in everything except content, and sources its content one of two ways:

  • "video": "<id>": the configuration borrows that video document's content and its share URLs.
  • Inline content: the configuration stands entirely on its own, used verbatim.
json
{
  "id": "portrait-standalone",
  "video": "quantify-genocide-impact",
  "config": {
    "version": 3,
    "layout": "portrait",
    "controls": { "fullscreen": false },
    "capabilities": { "chapters": true }
  }
}

Expansion rule: the demo tooling produces the final configuration by taking config, injecting the referenced video's content, and injecting its share URLs into capabilities.share (unless the configuration sets its own). The id and video keys are demo bookkeeping and are never part of the expanded configuration.

The configs page

The showcase renders each configuration shape as a live player next to its fully expanded JSON with a copy control. A video picker over the library re-expands the configuration with another video's data on the spot. What is displayed, and what copy copies, is always the complete, final-form configuration: paste it into any page, get this player.

The embed page

The showcase route /embed/<video-id>/ resolves <video-id> against the same data/videos/<video-id>/video.json library and mounts one landscape player that fills the viewport. The static deployment rewrites those document-shaped URLs to the shared embed entry without changing the browser URL. Showcase share configuration points its embed URL at this route so the generated iframe snippet completes the round trip on both local and deployed showcase origins.

The dummy feed

feeds/shorts.json holds { "items": [ …the ten portrait video ids… ] } in feed order. The feed page resolves the ids to documents, composes complete portrait configurations, and hands the feed module exactly its contract input (see the shorts document).

Media bucket

  • All binary assets (video files, posters, subtitle tracks) live in a Cloudflare R2 bucket, grouped by orientation: videos/<orientation>/<id>/poster.jpg, video.mp4, <lang>.vtt, with <orientation> being landscape or portrait. The repository library stays flat (data/videos/<id>/); ids are unique across orientations, and documents carry full URLs, so the grouping is a bucket-side convention only.
  • The bucket is managed as infrastructure, outside this repository: lt-demo-media, served at the public base URL https://demo.lt.org (see lt-infra docs/cloudflare-r2.md for the module definition and upload runbook).
  • CORS is a hard requirement: subtitle tracks are fetched with CORS, so the bucket must send Access-Control-Allow-Origin for the showcase origin and local development, and the player sets crossorigin on its media element. Plain video playback would work without it; captions would silently fail.

Fetching model

  • Documents, configuration shapes, and the feed file are fetched as static JSON from the showcase itself; the folder ships with the demo build. Media streams from the bucket.
  • Automated tests import the same files and treat media URLs as opaque values; tests never fetch media, so test determinism does not depend on the bucket.
  • Documents and bucket can drift (a document referencing an object that was never uploaded). The demo build guards against this: it HEAD-requests every media URL the documents reference and fails on a missing object.

Adding a video

  1. Create videos/<id>/video.json with the document.
  2. Upload the media to the bucket under videos/<orientation>/<id>/.
  3. Append the id to videos/index.json, and to feeds/shorts.json if it is a portrait short (portrait shorts must carry the full metadata set).

Adding a configuration shape

  1. Create configs/<id>.json, referencing a video or inlining content.
  2. Append the id to configs/index.json.