The browser plays the feed at ip:8080, and the viewer beside it draws a blank rectangle — or refuses the URL with an illegal-character error about the scheme name. Both failures are URL problems, not camera problems, and they usually arrive together. If you bind an IP camera's MJPEG feed into an HMI viewer, a SCADA client or your own grab loop, here is the ten-minute path: write the URL so a strict parser accepts it, tell the server's HTML page apart from the raw stream, find the real endpoint with two commands, and work down the short list of causes left when the picture still does not move.
The symptom splits into two causes
The browser is your proof that camera, network and server all work. What fails is the hand-off string: either the parser rejects it outright, or it names a page where the viewer wanted a stream.
| What you see | What it means | Fix |
|---|---|---|
IllegalArgumentException: Illegal character in scheme name at index 0 |
The URL has no scheme, so parsing fails on the first character | Write http:// (or rtsp://) in full |
| Browser plays, viewer blank, no error | You pasted the server's HTML page, not the stream | Point at the stream endpoint, for example /?action=stream
|
| One frame, then frozen | You used the single-snapshot endpoint | Use the multipart stream endpoint instead |
| Plays for minutes, then dies | Bandwidth, connectors or power | Work the list under "The URL is right and the viewer is still blank" |
Cause 1: the URL has no scheme
A browser is forgiving: type ip:8080 and it quietly tries http://ip:8080. URL parsers in Java-based software are not. A scheme must begin with a letter, and the parser demands one in the string; a bare address that starts with the digits of an IP has no scheme, so parsing stops at the first character. That is the index 0 in the message, and the component surfaces it as IllegalArgumentException.
The fix is one edit, made once and kept forever: write the scheme into every URL you store or paste. http://192.168.1.50:8080/?action=stream parses; 192.168.1.50:8080/?action=stream throws. The same discipline covers rtsp://, https:// and every other source string a viewer accepts.
Cause 2: the browser plays the shell page, not the stream
Most small MJPEG servers do not send video at the root. The root is an HTML page — a control or status page — and the video is embedded in it as an image element whose source points at the stream, for example /?action=stream. The browser fetches the page, follows that embedded source, and plays the video where the page puts it. A viewer component that plays MJPEG expects the stream bytes themselves; handed the root URL, it receives text/html, cannot render that as video, and shows a blank frame with no error at all.
You can see the difference in the browser. If the video sits inside a frame with controls, a title bar or styling around it, you are looking at a page. A raw MJPEG stream fills the tab edge to edge with nothing else.
Two ways out. Find the embedded address and give that to the viewer — the worked example below does exactly that. Or display the page as it is, in a widget that renders HTML: showing a camera live stream in an HMI web viewer widget covers that route.
One more endpoint trips people up: a single-snapshot address, the kind used for grabbing images from an IP camera over an HTTP snapshot URL, delivers one JPEG per request. That is right for timed polling and wrong for a live viewer, which plays one frame and freezes.
Worked example: find the stream URL for a camera at 192.168.1.50:8080
Inputs, and where each comes from:
- Camera at 192.168.1.50 (substitute your own address), stream server listening on TCP port 8080 — the port the browser was already playing in the case above.
- A PC on the same network with curl (present on current Windows and Linux installs; wget returns the same answers).
- Already proven by the browser: camera, link and server are all good.
Step 1. Ask the root what it serves:
curl -sI --max-time 3 "http://192.168.1.50:8080/"
The reply carries Content-Type: text/html. That is a page — the shell.
Step 2. Read the page and pull out the embedded address:
curl -s --max-time 3 "http://192.168.1.50:8080/" | grep -io 'src="[^"]*"'
On a server of the mjpg-streamer family this prints src="/?action=stream". Other firmware names the stream /video, /mjpg or a camera-specific path; the grep shows whichever this one uses.
Step 3. Prove the endpoint before typing it into the viewer:
curl -s --max-time 2 -D - -o /dev/null "http://192.168.1.50:8080/?action=stream" | head -n 8
The reply carries Content-Type: multipart/x-mixed-replace; boundary=.... That is the raw stream.
The decision rule is a threshold on one header: text/html means a shell page, keep looking; multipart/x-mixed-replace means the MJPEG stream — here http://192.168.1.50:8080/?action=stream, scheme included — is the URL the viewer needs.
The stream also has a price per viewer. The rule, in words: bandwidth equals frame size times frame rate, times eight bits per byte.
Assume 25 kB per 640×480 JPEG frame at 15 fps — a typical working pair of values for a small embedded camera server. Then 25 kB × 15 frames/s = 375 kB/s, and 375 kB/s × 8 bit/B = 3,000 kbit/s, about 3 Mbit/s per viewer. On a 100 Mbit/s wired link that leaves room for roughly 30 such viewers (100 ÷ 3 ≈ 33, held at 30 for margin); over Wi-Fi, budget a small fraction of that. Every extra consumer — the browser tab left open on the shop PC counts — adds another 3 Mbit/s to a small embedded server, so close the tab and let the HMI viewer be the only one.
The URL is right and the viewer is still blank
Work down this list in order; each test takes under a minute.
- The component does not accept MJPEG. Paste the stream URL itself — not the shell page — into a browser tab: edge-to-edge moving video proves the endpoint. If the endpoint is good, check which source types the component lists; some HMI widgets take RTSP only, and the same URL-in-the-binding-field discipline applies, as in IP camera widget shows no feed: put the RTSP URL in the binding field.
- The camera is on another subnet or behind a VPN. Run the curl header check from the viewer's own machine, not your desk PC. If the route is the problem, adding a remote subnet route for a smart camera over VPN is the fix to read.
- Too many consumers. Each viewer adds its own 3 Mbit/s in the worked example's case, and a browser tab is the heaviest viewer there is — why the browser is the most CPU-hungry MJPEG viewer puts numbers on it. Close every tab and second viewer; if the feed recovers, size the server to one consumer per stream.
- Connectors and power. A stream that dies when someone brushes the cabinet is a plug problem: an unlocked RJ45 walks out of its socket. Fit a cable that locks — the LS-CBL-0117 is a 15 m RJ45 camera data cable with a locking stud — keep the camera cables in the cell latched where they leave the cabinet, and pick a length that leaves no tension on the plug from the camera cable finder.
- Type
- Camera data cables
- Length
- 15 m
- Your own grab loop. If the blank window is your code, separate acquisition from rendering first — isolate capture from rendering when a camera window opens but no video shows walks that split. Then watch memory: an MJPEG response never ends, so a loop that allocates a new buffer per frame without releasing the old one leaks until the process dies, which fixing the per-frame image leak in a video loop covers.
Habits that prevent it
- Write the scheme into every saved URL — HMI binding, config file, panel schedule. Strict parsers never fill it in for you.
- Header-check any new endpoint once with curl before it goes into a viewer: text/html means keep looking, multipart/x-mixed-replace means use it.
- Record the stream endpoint next to the camera address on the panel schedule, so the next engineer starts from
/?action=stream, not from ip:8080. - Keep one consumer per stream on small servers, and make it the HMI viewer rather than a browser tab.
- If the feed drives a production decision, move it off the webcam-and-travel-router arrangement onto a purpose-built vision sensor on the wired plant network:
- Interface
- Fast Ethernet
- Resolution
- Up to 3 MP
- Interface
- Fast Ethernet
- Resolution
- 5 MP
Quick checklist
Before you touch the camera again, confirm:
- Scheme written out (
http://first) in the field the viewer reads — strict parsers do not fill it in. - Stream endpoint saved, not the root page — the root serves HTML the viewer cannot play.
-
curl -sIon the saved URL showsmultipart/x-mixed-replace— proof of what the viewer will receive. - One consumer per stream and browser tabs closed — each viewer costs its bandwidth share.
- RJ45 seated and locked, no tension on the lead — walked-out plugs look exactly like server faults.
- Stream port not forwarded to the internet — these servers rarely offer a login.
That is the whole repair: the scheme in the string, the endpoint instead of the page, one header check to prove it. For wider reading on viewers, streams and capture loops, the systems topic page collects the live guides; the smart cameras category is where a permanent feed starts, and the system configurator walks camera, viewer and cabling choices in order. If the headers still do not add up, capture the curl output and send it to us through the contact page.
Questions
Why does the browser play the feed while the viewer component fails?
The browser adds http:// by itself and renders the whole HTML page, video included. The viewer parses the URL strictly, so it needs the scheme written out and the stream endpoint, not the page. Treat the working browser as proof that camera, network and server are fine, and fix the URL string before touching anything else.
Which endpoint names do these camera servers use for the stream?
mjpg-streamer style servers answer on ?action=stream for the live multipart feed and ?action=snapshot for a single JPEG. Other firmware uses /video, /mjpg or a camera-specific path, so never guess: run the header check and trust Content-Type. text/html is a page; multipart/x-mixed-replace is the stream your viewer wants.
How much bandwidth does an MJPEG stream use per viewer?
Multiply kilobytes per frame by frames per second, then by eight: a 1280 x 720 stream at roughly 60 kB per frame and 20 fps works out to 60 × 20 × 8 = 9,600 kbit/s, about 10 Mbit/s per viewer. Ten viewers of that stream want around 100 Mbit/s, so keep multi-viewer video walls on wired Ethernet and off Wi-Fi.
Can I display the camera's control page instead of the raw stream?
Yes. A web-viewer widget renders HTML, so the shell page plays inside it with its controls intact. The cost is CPU: rendering a page weighs far more than decoding a stream directly, which matters on a panel machine already driving other graphics. Use a page view for a maintenance screen and point production viewers at the stream endpoint.
Is it safe to forward the camera's stream port to the internet?
No. These small HTTP servers rarely offer a login, so a forwarded port hands the feed and the camera's control page to anyone who finds it. Keep the camera on the plant network and reach it remotely through a VPN route. If the camera's own firmware documents an authentication option, switch it on as well.


