You connected a USB camera to a small Linux board, started the streaming service, and pointed the browser element of your web visualization at http://192.168.1.9:7070 — and got a download dialog instead of live video. If you wire camera previews into HMI screens, this failure has a short, checkable cause list: the element points at the server's page rather than its stream endpoint, the server answers with the wrong content type, or the embedded viewer cannot play a multipart stream at all. This guide walks the causes in the order you meet them, shows the header a real stream must carry, gives you a two-minute URL test, works the bandwidth arithmetic so you can size the network, and covers the snapshot fallback that plays in any viewer.
Why does the browser element download a file instead of playing the stream?
The element saves a file because the URL behind it answers with something no inline viewer can paint — a control page, a snapshot link, or an unnamed binary — and when a browser cannot render a response, its only remaining action is to hand it to the download manager.
An MJPEG stream is a third kind of answer: one HTTP response that never ends, whose body keeps replacing its own content. A browser plays it inline inside a plain image element, without JavaScript and without a player plug-in, but only when the response declares what it is. The screen you saw — the server's own page with its refresh and download actions — is the first sign that the element followed a page, not a feed. The download dialog is the second sign: the body arrived as an anonymous file, which happens when the content type says octet-stream or when the URL points at a file the server offers rather than the feed it streams. Nothing is wrong with the HMI project yet; the URL is answering with the wrong kind of response.
Which URL on the camera server is the stream?
The stream endpoint is the one that, opened alone in a desktop browser, starts moving video immediately with nothing else on the page; if a screen with buttons, settings or a title renders first, that address belongs to the server's control service, not to the camera feed.
Servers of this kind listen on two ports at once: one serves the control pages where you configure the service, the other serves nothing but frames. Your project used 192.168.1.9:7070 and got a page, so that port is doing control duty in this set-up. Many servers also publish the feed under a fixed path, often one ending in .mjpg, while others stream on whatever root path you request. The fastest way to find the feed is the server's start log or its configuration screen, which names the port it streams on; the viewer-shows-no-video guide walks the same hunt for other camera servers. Keep the two ports separate in your notes: the HMI element should only ever see the streaming one.
What does the HTTP response of a real MJPEG stream look like?
A streamable response declares the content type multipart/x-mixed-replace with a boundary marker, then sends JPEG frame after JPEG frame down one connection that never closes, each frame announced as image/jpeg between two boundary lines.
The anatomy is short enough to memorise:
A server that skips that header and answers application/octet-stream hands the browser an anonymous binary — the failure reported over and over by people writing their own MJPEG publishers, and the exact mechanism behind your download dialog. A Content-Disposition attachment header does the same damage. When you configure or write a publisher, the multipart content type is the one line that decides whether the browser plays the feed or files it.
How do you test the stream URL before touching the HMI project?
Two checks settle it in about two minutes: open the exact URL in a desktop browser, where moving video with no surrounding page proves it is the feed, then read the response header from a command line and look for multipart/x-mixed-replace.
- Open http://192.168.1.9:7070/ in desktop Chrome or Firefox. Moving video and no page furniture: this is the stream, and you can stop here.
- A page with controls instead? Read the server's start log or configuration screen for the streaming port and repeat the test on that port.
- From a PC on the same network, request just the headers (curl -sI is enough) and read the Content-Type line. A stream shows multipart/x-mixed-replace; text/html means a page; application/octet-stream means the download you have been seeing.
- Only now paste the URL into the browser element. If steps 1-3 pass and the element still downloads, the cause is the viewer, not the URL — the next section covers that case.
Why does the URL play in Chrome but still download inside the HMI?
Then the URL is proven and the limit is the viewer: browser elements inside automation runtimes are not full desktop engines, and some cannot consume a multipart stream at all — they fetch once, meet a body that never ends, and pass it to the download handler.
Confirm it in one step: if the same address plays on the engineering PC but downloads in the element, the engine inside the operator display is the difference. Where the display is a purpose-built unit — an industrial panel PC — ask the vendor which browser engine the runtime ships, because multipart support follows the engine, not the enclosure. You then have two paths that work everywhere. The steady one is to serve single JPEG snapshots and refresh the image element on a timer, using the cache-busting refresh pattern at the rates covered in the snapshot setup article. The other keeps the multipart feed and changes what serves it, as explained in the multipart versus single JPEG guide. Both play in every engine; only the refresh rate and the network load differ, and that load is the next number to check.
How much network does an MJPEG stream cost per viewer?
A 1280 × 720 colour feed at 10 fps moves about 1.1 MB of JPEG data per second, which is about 8.8 Mbit/s for every browser element watching, so four viewers load a 100 Mbit/s line with roughly 35 Mbit/s of its capacity.
JPEG at a server's default quality lands near 25:1 on a normal inspection scene. A noisy or detail-rich scene can double the frame to roughly 220 KB and push one viewer to about 18 Mbit/s; a static scene shrinks it toward half. Grayscale divides the raw frame by three instead of one (1280 × 720 × 1 B = 921,600 B), so a grayscale feed runs at about a third of the colour numbers.
| Feed | JPEG frame | Rate | Per viewer |
|---|---|---|---|
| 640 × 480 stream | ≈ 37 KB | 10 fps | ≈ 3.0 Mbit/s |
| 1280 × 720 stream | ≈ 110 KB | 10 fps | ≈ 8.8 Mbit/s |
| 1280 × 720 snapshots | ≈ 110 KB | 1 fps | ≈ 0.9 Mbit/s |
The decision falls straight out of the table: six to eight 720p viewers sit comfortably on one 100 Mbit/s switch port at 8.8 Mbit/s each, ten viewers push that link toward saturation, and anything on a 10 Mbit/s segment or a wireless hop should run 1 fps snapshots at 0.9 Mbit/s. The fuller cost comparison is in the stream versus snapshot article.
When is the camera the reason there is no stream to embed?
If the camera is a machine-vision camera — GigE Vision or USB3 — it publishes no HTTP page and no MJPEG endpoint at all: those cameras push raw frames to an acquisition library on a PC, so there is nothing camera-side for the browser element to point at.
That is an architecture difference, not a fault. The working pattern: the camera feeds a controller, the controller runs acquisition, and publisher code writes each frame out as a single JPEG that the HMI refreshes — the route in show a machine-vision camera image in a web browser HMI, with the acquisition-side options in snapshot URL versus raw TCP. If you are standardising the hardware for such a station, start from the board-level cameras and vision controllers categories, or open the camera finder:
- Sensor format
- 1/1.8"
- Interface
- USB3
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
The MV-CB060-10UM-B is a bare-board USB3 camera with a 1/1.8" sensor, and the MV-VC2140-128G40-1T is a fanless controller with 3-4 LAN ports and 2 USB 3 ports — enough for the camera, the machine network and the HMI network as separate links. If the same preview must also freeze moving parts for the operator, choose a global-shutter board camera: the LS-BCM-0003 captures 3.1 MP at 120 fps over USB3 with a C-mount lens mount.
- Sensor format
- 1/1.8"
- Lens mount
- C-mount
- Interface
- USB3
- Resolution
- 5 MP
Your publisher service then exposes those JPEGs over HTTP on the machine network, and from the browser element's side the set-up behaves exactly like an IP camera: one URL, one content type, one refresh timer.
How do you keep the stream working after the fix?
Pin the full stream URL — scheme, address, port and path — into the element, cap the feed at the frame rate the operator actually needs, and keep the server's control port off the HMI network; at 720p that budget is about 8.8 Mbit/s per element.
- Fix the complete address, port included, in the project and the documentation; a bare IP without the stream path is how this fault returns after an update.
- Size the feed before rollout: 5-10 fps is enough for a status view and halves the numbers in the table above.
- Put credentials on the control service and change them from the defaults, following the browser authentication guide.
- Give the publisher host a fixed address and a watchdog that pulls one frame per minute and alarms on failure; a fanless industrial mini PC fills that role next to the line.
- Re-run the two-minute header test after any camera server firmware or configuration change.
Once the URL plays, the rest of the screen is ordinary HMI work. The remaining streaming patterns — dashboards, restreams, widget quirks — sit on the systems topic page, and if you are planning a new station around this fix, the configurator walks camera, lens, lighting and controller choices in order.
Frågor
Does multipart/x-mixed-replace still work in current browsers?
Yes in desktop Chrome, Edge and Firefox: the mechanism is nonstandard but has rendered inside image elements for two decades, and it remains the way MJPEG addresses play inline. It fails in constrained viewers — some automation runtimes, kiosk shells and older embedded engines — which is why one URL can play on the engineering PC and download on the operator screen. Test the target screen's engine, not your own browser.
Can I put an RTSP camera URL into the browser element?
No. RTSP is a separate protocol on its own port, most often 554, and a browser image element cannot open it; the element falls back to a download or shows nothing. Use the camera's HTTP snapshot or MJPEG address instead, restream RTSP into HTTP inside your network, or open the feed in a standalone player beside the HMI window. Each route keeps the browser out of the RTSP path, because no engine will play it inline.
Why does my stream freeze after the first frame?
Usually something between the browser and the camera server buffers or closes the connection: a proxy, a virus scanner that inspects HTTP, or a server limit on simultaneous clients. A multipart stream is one open connection whose parts must keep flowing; an intermediary that converts it to a single buffered response leaves the image element showing frame one forever. Bypass the proxy for the camera's address to confirm, then add an exception.
How many camera streams can one HMI page show at once?
Budget by bandwidth rather than by a fixed count: at 720p and 10 fps every element pulls about 8.8 Mbit/s, so four streams take roughly 35 Mbit/s and fit a 100 Mbit/s line with room to spare, while ten streams near saturation. On a 10 Mbit/s segment or a wireless hop, run each feed as 1 fps snapshots at about 0.9 Mbit/s and stagger the timers so frames do not arrive together.
Will an HTTP camera stream load inside an HTTPS HMI page?
Browsers treat it as mixed content: a page delivered over HTTPS is not allowed to pull an HTTP stream, so the element shows an error or an empty frame rather than a download prompt. Most plant camera servers speak plain HTTP only. Either serve the HMI itself over HTTP on an isolated machine network, or put a small reverse proxy in front of the camera that ends HTTPS and forwards to the stream.


