MultiPOVRustWASMAndroidProductionAudio SyncAI-assisted development

MultiPOV after the PWA: turning a web idea into a Rust/WASM and Android media system

MultiPOV after the PWA

The first version of MultiPOV had a simple goal: let viewers watch several points of view from the same Discord session or event, aligned in time, with no account and no heavy setup.

That PWA is live at watchmultipov.app. It validated the product: YouTube, Twitch and Kick in the same control room, shareable sessions, an interface that is more than a wall of players, and VOD sync that works in a real browser.

Once that foundation shipped, the question changed. It was no longer only "how do I make a good PWA?" It became: how do I turn this web idea into a media system that can handle VOD, live, multiple providers, a native Android app and a serious release path?

1. The starting point: a PWA already in production

V1 was deliberately pragmatic. Solid, TypeScript, Vite, PWA, hls.js and session configuration encoded in the URL.

That let me ship without building an oversized platform. Videos and chats stay provided by the platforms. MultiPOV orchestrates the control room, layout, timeline and synchronisation.

The important point is that the PWA was not a disposable prototype. It was the first public product. Because it was in production, it made the next limits visible.

2. The browser limit

The browser is enough for many things, but live multi-provider sync quickly exposes its boundaries: iframe players, HLS, playback states that do not always behave the same way, variable latency, drift, source-specific capabilities and errors that need to be normalised.

In VOD, you can measure an offset, drive a clock and re-align. In live, you depend more heavily on what each source and player expose. The product therefore has to separate two concerns: measuring or initialising alignment, then maintaining a stable experience with a clock and offsets.

That is where the PWA alone became too narrow. I needed one media source of truth that would not be rewritten differently in each frontend.

3. The shared Rust core

The first TypeScript version used FFT.js client-side for GCC-PHAT measurement. It was useful: it proved the product principle and the user experience.

The current core is different. Media logic moved into a shared Rust engine, exposed as WASM on the web and compiled natively in the app. The reason is not to use Rust for its own sake. The reason is to keep the important rules in one place: timing, offsets, provider states, liveness, errors and fallbacks.

That reduces divergence. When a sync rule changes, it should not exist in two incompatible variants between web and Android.

4. Sync: measure, maintain, reattach

Sync is not one magic button. I treat it as three separate operations.

First, measure. GCC-PHAT estimates the offset between a stream and a shared reference audio. That is useful for initialising a VOD session or re-aligning a point of view.

Then, maintain. A shared clock and per-POV offsets drive playback: pause, seek, resume, layout changes, drift correction. It is less spectacular than audio correlation, but it is what makes the experience stable.

Finally, reattach. Some events require a fresh measurement or a correction: player state changes, a source restarting, live drift, or user interaction. The product has to accept these events instead of pretending they do not exist.

5. Integrating YouTube, Twitch and Kick without publishing a recipe

MultiPOV integrates YouTube, Twitch and Kick through a unified provider layer. What I want to show publicly is not a list of provider-specific tricks. The valuable part is the architecture.

Each provider exposes capabilities, liveness states, errors and fallbacks. These concepts are centralised so the web and native surfaces reason with the same vocabulary.

6. The native app: Android as proof surface

The native app is not a mobile landing page in disguise. It provides a real Android surface with Tauri 2, the embedded web control room, the shared Rust core and native Media3/ExoPlayer players.

That answers a concrete constraint: to prove live sync, I needed to leave the desktop browser boundary and measure on device. The Android app became a validation surface: installation, real behaviour, native players, logs, drift, corrections and stability during a live session.

7. Validating on the web and on device

The web product is in production and the Android app provides a complementary validation surface. Automated checks cover the points that actually break a version: primary journeys, assets, links, configuration and on-device behaviour. The proof therefore rests not only on the stated architecture, but on an experience exercised in both environments.

8. The role of Claude/Codex agents

Claude and Codex are not the product, but they are part of the working method. The most valuable loop is repeatable: specification, implementation, review, correction, validation.

On MultiPOV, I used that loop as a rigor mechanism. Claude helps produce and transform quickly. Codex also reviews, challenges, checks invariants, reads diffs, runs checks and forces concrete proof before a step is treated as done.

The short version is: specified, implemented, reviewed with Claude/Codex, verified in production and on device.

9. What I take from it as a product developer

MultiPOV is the project that best shows how I work: start from a precise use case, ship a real V1, then harden the architecture when the product justifies it.

The main lesson is simple: technical complexity is not product value by itself. Audio sync, Rust/WASM, Android or Media3 matter only because they serve a clear experience: open several POVs and watch them together, properly synchronised.

That is also what I want this portfolio to show: I do not only build screens. I can frame a product, write the system that makes it possible, put it in production and validate it under real conditions on the web and on device.