Streaming API (Native Player)
App Container provides web developers with two distinct ways to display IP camera video streams:
- Native Player (
appcontainer.streamBridge): A Flutter-native video overlay powered bylibmpv/FFmpeg. Ideal for dedicated kiosk touch panels, offering zero added latency, hardware decoding, and raw performance. - WebRTC / WHEP Proxy: Bridges RTSP to WebRTC for viewing in standard web browsers without plugins. (See WebRTC / WHEP Proxy Documentation).
🛠️ How appcontainer.streamBridge Works
The app exposes a JavaScript bridge object at window.appcontainer.streamBridge. Sending JSON messages through postMessage creates, updates, or destroys a native video surface rendered directly over the WebView at the exact location of a target HTML <div> or specific screen coordinates.
🚀 JavaScript API Reference
1. Opening a Video Stream
To open an RTSP stream and map it onto an existing DOM element on your page:
window.appcontainer.streamBridge.postMessage(JSON.stringify({
action: 'open',
id: 'cam1', // Unique stream identifier (string)
url: 'rtsp://192.168.1.10:554/stream',
divId: 'player-div' // ID of the target DOM <div> element
}));
The native player calculates the bounding rectangle of #player-div and aligns the hardware video surface precisely on top of it.
Note: The placeholder
<div>itself remains untouched by the native layer. You can style the<div>with CSS (e.g., loading spinner, placeholder background, or borders) while the stream initializes.
Absolute Screen Coordinates (rect)
Alternatively, specify overlay dimensions in fractional screen coordinates (ranging from 0.0 to 1.0):
window.appcontainer.streamBridge.postMessage(JSON.stringify({
action: 'open',
id: 'cam1',
url: 'rtsp://192.168.1.10:554/stream',
rect: { x: 0, y: 0, w: 1, h: 0.5 } // Top half of the screen
}));
2. Handling Scroll and Window Resize
When page elements shift (due to layout changes, scrolling, window resizing, or DOM mutations), update the native overlay position by posting a resize action:
// Attach a ResizeObserver to the target container <div>
const playerDiv = document.getElementById('player-div');
const observer = new ResizeObserver(() => {
window.appcontainer.streamBridge.postMessage(JSON.stringify({
action: 'resize',
id: 'cam1',
divId: 'player-div'
}));
});
observer.observe(playerDiv);
Using ResizeObserver ensures the video surface automatically tracks element layout changes smoothly.
3. Closing Video Streams
Close individual video overlays or terminate all active streams simultaneously:
// Close a specific stream by ID
window.appcontainer.streamBridge.postMessage(JSON.stringify({
action: 'close',
id: 'cam1'
}));
// Close all active streams
window.appcontainer.streamBridge.postMessage(JSON.stringify({
action: 'closeAll'
}));
4. Multiple Simultaneous Streams
You can render multiple active streams on the same screen by providing a unique id for each instance:
const cameras = [
{ id: 'cam1', url: 'rtsp://192.168.1.10:554/stream1', divId: 'div-cam1' },
{ id: 'cam2', url: 'rtsp://192.168.1.11:554/stream1', divId: 'div-cam2' },
{ id: 'cam3', url: 'rtsp://192.168.1.12:554/stream1', divId: 'div-cam3' },
{ id: 'cam4', url: 'rtsp://192.168.1.13:554/stream1', divId: 'div-cam4' }
];
cameras.forEach(cam => {
window.appcontainer.streamBridge.postMessage(JSON.stringify({
action: 'open',
id: cam.id,
url: cam.url,
divId: cam.divId
}));
});
Note: While there is no software limit on the number of streams, real-time decoding performance depends on device hardware acceleration and stream resolution.
🔄 Navigation & Lifecycle
All active native video overlays are automatically destroyed whenever the WebView navigates to a new page or reloads.
If your web app uses multi-page navigation, re-initialize your streams in a pageshow or DOMContentLoaded event listener:
window.addEventListener('DOMContentLoaded', () => {
if (window.appcontainer?.streamBridge) {
// Re-initialize stream player
}
});