Engineering Playbook

Offline-first signage design

Design signage that keeps playing through network drops, flaky Wi-Fi and power bounces. This guide covers architecture, manifests, caching, sync, player state machines, observability, QA and rollout.

Principles

  • Local is primary: the player must never block on network for playback.
  • Deterministic: schedules and transitions produce the same output online or offline.
  • Recoverable: every step is crash-safe and idempotent.
  • Observable: logs, heartbeats and proof-of-play survive outages.

1) System architecture

On-device

  • Player app (Tizen/webOS/Android) with AV layer (AVPlay/MediaPlayer).
  • Local content store with checksum-verified assets.
  • Scheduler & state machine plus background sync worker.
  • Health monitor handling heartbeat, temperature, free space and crash counter.

Backend

  • Content API serving signed manifests and assets.
  • Device registry & policy (model/firmware channels, time zone).
  • Telemetry ingest & proof-of-play store.

2) Content manifest

Manifests make offline decisions deterministic. Serve a compact JSON that lists assets, versions, checksums, durations and schedule.

{
  "version": "2025.07.15-09",
  "valid_from": "2025-07-15T09:00:00Z",
  "assets": [
    {"id":"hero-8k","path":"loops/hero_8k.mp4","sha256":"...","bytes":123456789,"duration":120},
    {"id":"promo","path":"loops/promo_4k.mp4","sha256":"...","bytes":45678901,"duration":30}
  ],
  "schedule": [
    {"asset":"hero-8k","start":"* 08:00","end":"* 20:00"},
    {"asset":"promo","pattern":"*/15m"}
  ]
}

Use semantic version strings for rollout control. Include bytes to pre-allocate space and detect truncation.

3) Caching strategy

Storage layout /wgt-private/content/<version>/ (Tizen/webOS) or app-private directory (Android).
Atomic updates Download to .part, verify checksum, then rename. Keep previous version as fallback.
Space management Budget by manifest bytes; apply LRU eviction for expired assets.
Integrity sha256 per file; verify on boot and before playback.

4) Sync & scheduling

  • Intervals: poll manifests every 5–15 minutes; back off when offline.
  • Time: maintain device time via NTP or gateway sync; fail gracefully with last known schedule.
  • Partial downloads: keep the current playlist running while background fetch continues.
  • Regionalisation: select channel by tags (store-id, timezone, locale).

5) Player state machine

States: IDLE → PREPARE → PLAYING → LOOPING → ERROR → RECOVER

On PREPARE: verify file, open decoder, warm-up audio
On LOOPING: if streamcomplete → seek(0) → play()
On ERROR: log, increment counter, fallback to safe asset, schedule retry
Watchdogs: decode stall, no frames for 2s, CPU temp high → restart AV

Tizen AVPlay (local file)

const p = tizen.tvavplay;
p.open('file://wgt-private/content/2025.07.15-09/hero_8k.mp4');
p.setDisplayRect(0, 0, screen.width, screen.height);
p.prepareAsync(() => p.play());
p.setListener({ onstreamcompleted: () => { p.seekTo(0, () => p.play()); } });

webOS (webOS.video API)

// pseudo
video.src = 'file:///media/sereno/content/2025.07.15-09/hero_8k.mp4';
video.loop = true; // with exact muxing this is seamless
video.play();

6) Telemetry & proof-of-play

  • Heartbeat: every 60s (device id, app version, uptime, free space, temperatures, Wi-Fi RSSI).
  • Event log: play_start/stop, errors, retries, version_switched.
  • Proof-of-play: CSV/JSON rows with timestamps, asset id, duration watched.
  • Store-and-forward: queue locally; upload when back online; dedupe by idempotency key.

7) Security & hardening

  • Sign manifests; optionally sign assets or deliver over mutually authenticated TLS.
  • Lock kiosk mode and restrict remote control to your secure channel.
  • Validate filenames/paths from the manifest and deny traversal.
  • Stagger updates to avoid fleet-wide spikes.

8) QA & soak testing

  • 72-hour soak per model/firmware channel; power-cycle and network-bounce.
  • Record decode metrics, temps, reboot count and playlist accuracy.
  • Simulate corrupted downloads to confirm rollback logic.

9) Rollout & ops

  1. Stage on a small canary group per model.
  2. Observe telemetry for 24–48 hours.
  3. Gradually expand by region/timezone.
  4. Rollback by pinning the previous manifest version.

Appendix: snippets

Exponential backoff (fetch manifest)

async function fetchWithBackoff(url, max = 6) {
  for (let i = 0; i < max; i++) {
    try {
      const res = await fetch(url, { cache: 'no-store' });
      return await res.json();
    } catch (e) {
      await new Promise(r => setTimeout(r, Math.min(60000, 2000 * Math.pow(2, i))));
    }
  }
  throw new Error('failed');
}

Atomic download with checksum (pseudo)

download(url, tmp)
if (sha256(tmp) === manifest.sha256) rename(tmp, target)
else delete(tmp) & retry