Skip to main content

Streaming API (Native Player)

App Container provides web developers with two distinct ways to display IP camera video streams:

  1. Native Player (appcontainer.streamBridge): A Flutter-native video overlay powered by libmpv/FFmpeg. Ideal for dedicated kiosk touch panels, offering zero added latency, hardware decoding, and raw performance.
  2. 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
}
});