System designHow-to

Embed a live MJPEG stream in an HMI page that survives remote access

Embed the camera's MJPEG stream in an HMI image element, and rewrite the URL through the remote-access proxy whenever the page opens from outside the LAN.

LumaSpec Vision12 min read Share on LinkedIn

On this page 10 sections
  1. 01How the image element plays the stream
  2. 02Find the camera's stream URL first
  3. 03Wire the stream into the HMI page
  4. 04Why the LAN URL fails from outside
  5. 05Failure table: symptom, cause, fix
  6. 06Size the link: a bandwidth budget
  7. 07Keep the stream alive through gateway timeouts
  8. 08When the camera has no web address
  9. 09Before you ship: the checks
  10. 10Questions

A camera view on an HMI screen is one line of work on the bench and a different line over remote access. Point an image element at the camera's multipart/JPEG address — http://192.168.3.10/mjpg/video.mjpg is a typical shape — and the stream plays in any browser on the plant LAN. Open the same page through a remote-access gateway and you get a 404, a grey box, a picture that takes minutes to arrive, or one that dies after seconds. The fix is not a different player. It is two URLs, one per access path, and a short script that rewrites the element's source when the page opens from outside. This guide gives you the URL rules, the switching script, a bandwidth budget with the arithmetic shown, and the failure table to work through when a streamed view misbehaves.

How the image element plays the stream

A browser needs no video plugin for MJPEG. The camera answers an ordinary image request with a multipart/x-mixed-replace body and never closes the connection; each frame arrives as its own JPEG and the image element swaps what it shows. Chrome, Firefox and Edge render this inside a plain image element, which is why one URL works in an HMI web view, an SVG page or a dashboard panel. The connection stays open as long as the page does, and it appears in the browser's network tools as a single request that never finishes. That one pending request is your diagnostic for everything in this guide.

Desktop SCADA viewers without a native web view can still host the page: one common package embeds web content through an ActiveX browser control. The stream rules below are the same inside that window.

Find the camera's stream URL first

Log into the camera's own web page in Chrome or Firefox and let its live view play. Open the developer tools, switch to the network tab, and find the request whose type reads multipart and whose status never completes: its address is the stream URL. Many network cameras serve a fixed path such as /mjpg/video.mjpg; others run a snapshot-style script such as /video.cgi with the login carried in its query string. If the camera advertises RTSP instead, an image element cannot render that stream — the RTSP stream in an HMI guide covers the bridge options.

Prove the endpoint before the HMI gets involved: paste the URL into a bare browser tab. A correct multipart endpoint shows a picture that keeps updating and a tab that never finishes loading. One frozen frame or an error means the address itself is wrong — the MJPEG viewer troubleshooting guide walks that failure, and the MJPEG CGI URL finder helps when the camera hides its path.

Wire the stream into the HMI page

You need three things: the camera's stream URL, the gateway's LAN address, and the proxy path its remote-access pages use for LAN devices. The plain on-site embed is covered step by step in Embed an IP camera stream in an HMI or SCADA window; what follows is the two-sided version that also survives remote access.

  1. Collect both addresses: the camera's LAN URL, http://192.168.3.10/mjpg/video.mjpg in this example, and the gateway's proxy path for reaching a LAN device through its own pages.
  2. In the HMI editor, place a web view or image element where the video goes and note its element id — the script sets the source on that id.
  3. Set the source by script, never as a fixed address on the element, so one page serves both sides of the gateway.
  4. For the remote branch, use the proxy path exactly as the gateway's own pages use it. On gateways that proxy LAN devices through their cloud address, the pattern that holds is a relative prefix such as ../../proxy/, then the camera's LAN IP, then the stream path. Hand-building the absolute cloud URL — scheme, cloud host, proxy segment — returns 404 within a second.
  5. Decide how the login travels. The remote branch borrows the gateway's own authenticated session; the LAN branch may prompt once for the camera's credentials. If the camera only accepts credentials inside the URL, see MJPEG stream URL with username and password — and read the note below before you ship one.
  6. Test both sides. On the LAN, the page should show the stream with one pending multipart request aimed at the camera IP. From a phone on mobile data through the remote-access service, the same test must show the request aimed at the gateway host instead.

The script is small. The values are illustrative — rename ids and addresses to your own:

// string: id of the image element in your HMI page
var streamUid = "UID_1630463848146";
// string: camera address on the plant LAN
var cameraIp = "192.168.3.10";
// string: the camera's multipart/JPEG path
var streamPath = "/mjpg/video.mjpg";
// string: the gateway address as the browser sees it on the plant LAN
var gatewayLanHost = "192.168.3.2";

// string: built per access path
var url = "";
if (window.location.host.indexOf(gatewayLanHost) !== -1) {
  // Browser reached the gateway on the LAN: talk to the camera directly.
  url = "http://" + cameraIp + streamPath;
} else {
  // Page came through the remote-access service: send the image back through the gateway.
  url = "../../proxy/" + cameraIp + streamPath;
}
document.getElementById(streamUid).src = url;

An SVG-based view has no src property on its image node; set the xlink:href attribute to the same url string. The host check is plain string matching, so match on whatever host string your gateway presents on each side, and keep both branches in one script so nothing is hard-coded to one access path.

Why the LAN URL fails from outside

The remote failure is not flaky wireless; two hard rules produce it. First, 192.168.3.10 is a private address that exists only inside the plant network, so a browser at home has no route to it — the request cannot even leave your carrier. Second, mixed content: the remote page arrives over HTTPS, and a browser will not quietly load an http:// image from an https:// page. It upgrades the request to HTTPS, which the camera cannot answer, or blocks it outright. Newer browsers also gate requests from a public page to private addresses.

The gateway's proxy path removes both blockers with one rewrite. The image request goes to the same HTTPS host that served the page, so it is same-origin; the gateway then fetches the camera over its own LAN connection, where 192.168.3.10 is reachable. That is why the on-site branch may keep the plain http:// camera URL while the remote branch must not.

If the plant reaches in through a VPN client rather than a cloud gateway, the picture inverts: once the subnet route exists, the browser is effectively on the LAN and the direct camera URL works from anywhere. The VPN subnet route fix shows how to add that route.

Failure table: symptom, cause, fix

Work the table top down when a streamed view misbehaves; the first matching row is usually the cause.

Symptom Likely cause Fix
404 within a second, remote only Hand-built absolute cloud URL, or wrong proxy prefix Use the gateway's own proxy path; relative ../../proxy/ + camera IP + stream path is the pattern that holds on common gateways
First frame takes minutes The element or a wrapper waits on a second connection while the first hangs Point the element straight at the URL — src, or xlink:href in SVG — and confirm it in a bare tab first
Plays, then dies after seconds The gateway closes a long-lived multipart connection on its own timeout Re-set the source with a cache-busting query, or fall back to snapshot polling (next section)
Grey box, on the LAN too HTTPS page calling an http:// camera URL, or a widget that cannot hold connections open Route the LAN branch through the proxy as well; the grey HMI camera widget guide covers the branch points
One corrupted image made of two half-pictures Two JPEG responses concatenated into one buffer Give every element its own stream URL; the two-SOI-markers JPEG error guide explains the signature

Desktop SCADA adds its own URL and authentication settings on top of these; the SCADA IP camera MJPEG configuration and troubleshooting page walks the ones that differ per package.

Pick frame rate with the link in mind. A served JPEG frame lands near 0.1 to 0.2 bytes per pixel at the quality cameras typically deliver; plan on 0.15 until you have measured your own frames. The budget then follows from two lines:

frame_bytes ≈ width_px × height_px × 0.15 bytes/pixel bitrate ≈ frame_bytes × fps × 8 bits/byte

Worked example, one 1920 × 1080 stream at 15 fps:

  • 1920 × 1080 = 2,073,600 pixels
  • 2,073,600 × 0.15 = 311,040 bytes, about 311 KB per frame
  • 311,040 × 15 = 4,665,600 bytes/s, about 4.7 MB/s
  • 4.7 MB/s × 8 = about 37 Mbit/s per camera

Two cameras in one remote session cost about 75 Mbit/s: enough to crowd a 100 Mbit/s plant link and to dwarf a 10 to 20 Mbit/s uplink. To fit a 20 Mbit/s uplink shared by two cameras, give each about 10 Mbit/s and cut frame rate first: 15 fps × 10 ÷ 37 ≈ 4 fps per camera. Five frames per second still reads as live on a status screen, while halved resolution is gone for good. A heavy stream also drags the whole page, not just the video window — the stream versus snapshot slowdown guide shows when to switch — and the network tools' size column gives you your real bytes per pixel within a minute.

Keep the stream alive through gateway timeouts

Some gateways end a long multipart response after an idle or total-time limit; a stream that loads after minutes and dies after seconds is usually this single cause. Two client-side fixes, in order:

  1. Re-set the source. Assigning the URL to the element again, with ?t= plus a timestamp appended so the request is not served from cache, tears down the old connection and opens a fresh one without a page reload. Trigger it from the element's error event and from a watchdog timer; 60 seconds is a practical starting point.
  2. Fall back to snapshot polling. If the gateway will not hold the connection at all, point the element at the camera's single-frame JPEG address and refresh it on a timer. At 500 ms to 1 s per frame, a status screen reads as live video, and every frame is one short request the proxy handles easily.

The snapshot polling how-to and the remote dashboard variant give the timer code; keep the watchdog independent of the HMI's own refresh logic so one stuck element cannot stall the screen.

When the camera has no web address

A GigE or USB3 area scan camera does not publish an MJPEG URL. It ships frames over its machine-vision protocol to an SDK on a PC, and nothing in that exchange speaks HTTP. The pattern that closes the gap is a small frame server on the receiving PC — grab a frame from the SDK, encode JPEG, answer browser requests with a multipart body — after which every rule in this guide applies unchanged, proxy rewrite included. The working code is in Stream camera frames in Flask with multipart JPEG, and the full camera-to-web path is in Embed a machine vision camera stream in a web HMI.

For a status view that also does presence checks, 2.5 MP over GigE is plenty. The MV-CU020-90GC is a 2.5 MP GigE color camera with a 1/1.7" sensor format, a C-mount and a global shutter.

Area scan camerasHikrobot MV-CU020-90GC 2.5 MP Area Scan Camera, GigE, GMAX4002, ColorMV-CU020-90GC
Sensor format
1/1.7"
Lens mount
C-mount
Interface
GigE
Resolution
Up to 3 MP
$309.00View partBuild a system with it

When the same feed also carries measurement, step up in resolution and let the frame server downscale the live view while the inspection takes full frames. The MV-CS200-10GC is a 20 MP GigE color camera with a 1" sensor format, a C-mount and a rolling shutter.

Area scan camerasHikrobot MV-CS200-10GC 20 MP Area Scan Camera, GigE, IMX183, Color, BasicMV-CS200-10GC
Sensor format
1"
Lens mount
C-mount
Interface
GigE
Resolution
16-25 MP
$739.00View partBuild a system with it

Where the decision belongs inside the camera itself, the smart cameras category runs inspection on the device with no PC in the loop.

Code readers & vision sensorsSmart cameras & vision sensors

Inspection on the device, no PC

350 models · from $169.00

To choose the camera side from scratch, the camera finder filters by interface, resolution and shutter, and the camera configurator walks the whole chain.

Before you ship: the checks

Run this list against the page before an operator sees it. Each check exists because skipping it shows up as one of the table symptoms above.

  • The stream URL, pasted into a bare tab, shows a picture that updates forever — the endpoint is proven before anything wraps it.
  • On the LAN page, the network tools show exactly one pending multipart request, aimed at the camera IP.
  • Through the remote-access service, that request is aimed at the gateway host instead, with status 200 — never at the camera's private LAN address.
  • The error handler and a 60-second watchdog re-set the source with a fresh timestamp, so a proxy timeout self-heals.
  • Measured frame size × fps × 8 sits under half of the narrowest link the page will meet.
  • No shipped URL carries a username or password, and the camera has no port-forward to the internet.

If a stream still misbehaves after the table, the systems topic page holds the neighbouring fixes, and the contact page is the route to us with your gateway and camera pair — the remedy is usually one line of URL.

Questions

Why does my MJPEG stream work on the plant network but not from home?

Three separate blocks stack up. A home PC cannot route to a private address such as 192.168.3.10 at all, an HTTPS-hosted page refuses to load an http:// camera subresource as mixed content, and the camera would never see your gateway login anyway. Send the image request through the gateway's authenticated proxy path instead of the bare camera URL and all three disappear.

How much bandwidth does one MJPEG stream need?

Plan on 0.1 to 0.2 bytes per pixel per JPEG frame. A 1280 × 720 stream at 10 fps needs about 11 Mbit/s: 921,600 pixels × 0.15 bytes is about 138 KB per frame, × 10 fps ≈ 1.4 MB/s ≈ 11 Mbit/s. Dropping a 1080p stream from 15 to 5 fps cuts it from about 37 to 12 Mbit/s.

Can I put the camera's username and password in the stream URL?

Most cameras accept credentials as URL parameters and the stream plays. Avoid it on a shipped page: the credentials end up in browser history, proxy logs and the page source anyone can open. Prefer the gateway proxy, which holds the login server-side, or a snapshot poll against a session the page authenticates once.

Why does the stream freeze after a few seconds through remote access?

Some remote-access gateways close or throttle a long-lived multipart connection after an idle or total-time limit, leaving the last frame frozen on screen. Assigning the stream URL to the image element again, with a cache-busting timestamp, opens a fresh request without a page reload. If the gateway still drops it, switch that view to timed snapshot polling.

Do machine-vision cameras have an MJPEG web address?

No. A GigE or USB3 area scan camera streams raw frames over its machine-vision protocol to an SDK on a PC, not multipart JPEG to a browser. Run a small frame server that grabs frames, encodes JPEG and serves them on your own URL; from there the embed and proxy rules are identical.

Which browsers can show a multipart JPEG stream in an image element?

Chrome, Firefox and Edge all draw a multipart/JPEG response inside a standard image element, and most HMI web views use that element under the hood. Exceptions are older embedded browsers and some locked-down widgets — a grey box or one frozen frame is the tell. Verify in the exact widget you ship, and keep a snapshot-poll fallback ready.