Small additions the mobile app (app/) would like. Everything here is optional: the app already works without it and falls back gracefully.
The app shows /player/:id?token=&quality=hd|sd&muted=1&embed=app in a WebView.
- When running inside the app (
window.ReactNativeWebViewexists), please post state changes:window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'state', state: 'loading' | 'playing' | 'error', mode: 'webrtc' | 'mse' | 'hls', error?: string }))The app currently also polls the first<video>element (!paused && readyState >= 2) as a fallback, so this is a nice-to-have. - Please expose a tiny control object:
and post
window.OpenCCTV = { setMuted(muted: boolean): void, talk(on: boolean): Promise<void>, // push-to-talk: getUserMedia({audio:true}) and send the track over the WebRTC peer (sendrecv audio), stop/disable it on false setQuality(q: 'hd' | 'sd'): void, }
{ type: 'talk', state: 'on' | 'off' | 'error', error?: string }back. - With
embed=app: no on-page controls/overlays (the app draws its own),object-fit: contain, black background.
Camera.capabilities.ptzPresets?: { id: string; name: string }[] (from ONVIF GetPresets). The app shows them as buttons and calls POST /api/cameras/:id/ptz { action: 'preset', preset: id }. Without it the app shows nothing for presets.
For /api/push/register notifications, please put this into the Expo push message data so tapping opens the right recording:
{ cameraId, eventId, start /* ISO */, server /* public base URL the app used, e.g. https://gw/s/<siteId>, if known */ }
Title: camera name, body: e.g. "Motion detected", channelId: 'motion' (the app creates this Android channel), sound: 'default'. Optional mutableContent + richContent.image with the event snapshot URL.
Field could carry optional help?: string and default?: string (the app renders them under the input if present).
On the iOS Simulator the full-screen /player/:id?embed=app (WebRTC) shows black for roughly 1 to 2 seconds every time a demo clip loops (ffmpeg -stream_loop -1 -c copy), e.g. every 13 s for backyard.mp4. Recordings and HLS are not affected. Generating continuous timestamps for the loop (for example -fflags +genpts with -re, or re-muxing the clips once into a long file) would avoid the gap on the public demo. App side nothing to do.
serveFile sets content-length for 206/200, but the response arrives as Transfer-Encoding: chunked without Content-Length (checked with curl -D - -H 'Range: bytes=0-' …/video.mp4; probably dropped where security headers are added to the response). AVPlayer on iOS copes, but on the Android emulator ExoPlayer stalls on the first frame of recordings while HLS live plays fine. Please keep Content-Length on range responses (and on HEAD), so progressive MP4 playback and seeking work reliably on Android.