MultiPOV after the PWA: a Rust core shared by web and Android
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 brings YouTube, Twitch and Kick into one viewer, with shareable sessions, configurable layouts and VOD synchronisation in the browser.
The next step was to support VOD, live playback and multiple providers, then share that logic with an Android app and prepare its releases.
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.
Use of that first public version revealed synchronisation limits.
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 engine centralises the media rules: 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
I distinguish three synchronisation 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. This logic maintains alignment after interactions.
Finally, reattach. Some events require a fresh measurement or a correction: player state changes, a source restarting, live drift, or user interaction. These events require a new measurement or realignment.
5. A shared provider layer
MultiPOV integrates YouTube, Twitch and Kick through a unified provider layer. Provider-specific integration details stay behind that layer.
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. Testing synchronisation on Android
The Android app uses Tauri 2, the embedded web control room, the shared Rust core and native Media3/ExoPlayer players.
That answers a concrete constraint: to check live synchronisation, 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 checks cover scenarios run in each environment.
8. Working with agents: addition dated 8 October 2026
Since writing this article, I have clarified the tools and access available to Codex and Claude Code to work on Android. I prepare context, tasks, criteria and access before implementation. The MCP connected through CDP can drive the WebView; ADB complements diagnosis with identification of the installed APK and native logs.
The current loop is: implement → test the WebView through MCP/CDP on an identified build → consult native logs through ADB when needed → correct → retest. A correct DOM state does not prove correct native video rendering. Review must also check that the harness actually observed the phenomenon being tested before accepting its verdict.
I retain decisions and acceptance of results. Conversational scoping, agent preparation and the loops specific to MultiPOV, Aidventures and Nomad are detailed in Developing with AI agents.
9. What I take from it
MultiPOV shows my progression: start from a specific use case, release a V1, then adapt the architecture to the limits encountered. The shared Rust core and Android players serve the same need as the first PWA: watching several POVs together, synchronised. My role includes scoping, integration decisions and validating agent results.