So, I’m learning Flex at long last. The reason for me finally taking up this challenge is that I finally found a project I believed it well suited to. You see, my family has lots of digital photos. About 9000 at last count. We keep these stored on a NAS in my office. All of our computers are set to show these photos as our screen savers. However, I’ve wanted a nice wireless digital picture frame that would randomly pull from this pool of images. Well, to make a long story short, I was unable to find a single wireless picture frame that could read from a Samba share. (And trust me, I’ve tried a lot of things.)
So, finally I became frustrated enough to take this into my own hands. I plan make my own ideal digital picture frame. I’m currently working on an Air application that will run in full screen and provide access to a range of picture frame components. Obviously, a part of this will display pictures. Other components will display weather information, stats on Alagad, and pretty much anything else I want to wire into my picture frame. Eventually, I’ll buy a nice, thin, touch screen tablet PC, configure it to run my air app on load, get it professionally framed and hang it on the wall.
Now, one of the components I want this picture frame to show is a video stream from my daughters baby monitor camera. Actually, it’s not really a baby monitor camera it’s a more-or-less generic wireless surveillance camera. I don’t have the model information any more and it’s not branded at all. But, from research I’ve done there are a lot of these cameras which run the same software.
The camera runs a basic web server so I can pull up the camera and see either a still photo or a video stream in a Java Applet or ActiveX Control. I did a little playing around with the camera and discovered that the still photo is returned from a file called “image.jpg”. Any time you request this file you get the latest image from the camera. So, my first attempt a hooking into the video camera was simply to request this photo over and over again and use that as the source for an Image object. This worked to an extent, but the Image object wouldn’t refresh quickly enough and would as a result flicker. Beyond that, I had to make a complete HTTP request for each frame of the video and it had a very slow refresh rate. I was able to solve the flickering problem by having two images on my canvas and alternate setting the source of each image and hiding the other image.
I was happy to have accomplished that, but it just wasn’t very nice. So I decided to do a little more research. The Java Applet on the camera was called xplug.class. Googling that revealed a few people who had decompiled the class and used that to discover that the camera actually streams video from a file named mjpeg.cgi. If you access this file directly you’ll simply get a long stream of binary data which makes up the video feed from the camera. Obviously the Java Applet and Active X Control could read this stream and use it to display the video feed.
A little more research revealed that, sadly, mjpeg is not a standard but more of a technique. Mjpeg really is just a concatenated stream of jpeg images, one after another. I’m familiar with the JPEG format (at least to some degree) from my work on the Image Component. At least, I knew enough to know the first few bytes that mark a JPEG image (FF D8). So, I figured I’d look at the data in the video stream to see if I could figure it out. I captured a few seconds of the video to disk using Curl and opened it up in a hex editor. The first thing I noticed was that each frame of the video was denoted by an ascii string “–video boundary–“. Looking a bit further into the file I discovered that I could find the markers that start an image. I tested cutting the bytes from the beginning of where I thought the JPEG would start to the last byte before the video boundary into a new file and saved it as a jpeg, which I was subsequently able to read!
I knew that if I could read the feed by simply starting at the top of the feed, find the start of an image and the end, cutting out that image, displaying it and repeating the process forever.
Simeon Bateman filled in the last little piece of data I needed to know to make this work. Namely, that you can simply provide binary data to the source property of an Image object and Flex will display the image.
So, after figuring out how to make a socket connection from Flex I was able to parse the video feed and display the video feed! I did still have the flickering problem but I solved it in a similar manner.
Attached to this blog entry is a final Flex Component which connects to the camera by URL and streams the feed. Anyone who has a use or it may use it, assuming your video feed is the same format as mine. This is my first real bit of AS3/Flex development though so don’t be offended if I’m not doing things in the best way possible.
Here’s an example usage of the component:
<webcam:webcamImage left="0" top="0" right="0" bottom="0" host="mycamera.com" port="80" />
You can also supply username and password properties to access password protected cameras.
Comments on: "Viewing MJPEG Streams in Flex" (29)
From someone that knows little/nothing about the capabilities of digital frames: Are there digital frames that can run Air apps? Or for that matter one that supports the Flash Player? I know some of the Sony frames run Windows but unclear what you can actually do with them in terms of apps.
LikeLike
I’ve not seen anything that runs a real version of Windows. I don’t believe there are any standard consumer frames that can run air apps.
LikeLike
Thanks for confirming. An “AirFrame” would be nice but probably ends up with hardware being at least a netbook (eeePC).
LikeLike
Doug, your attachment seems to be broken, I guess the webtier compiler is trying to compile it instead of allowing the download.
LikeLike
@Joo – Thanks, I didn’t test the download. I’ve zipped it and you can get it now.
LikeLike
The camera data is multipart HTTP, in this case also known as MJPEG. There is no need to guess at the JPEG location in the stream. See “Motion JPEG Formats” section of http://www.jpegcameras.com/. Thanks for the component, I’m also new to Flex and needed something like this.
LikeLike
Hi,
I have problem with access streams from remote web server. crossdomain.xml file is ok, I can access other media files – such as normal JPGs via Image component. But your component fails with Error #2044: Unhandled securityError:. text=Error #2048: Security sandbox violation: http://mysite/cross/CrossRoads.swf cannot load data from mysite:80.
at webcamImage()[C:JavaworkspaceFlexCrossRoadssrcwebcamImage.mxml:15]
at mx.core::Container/createComponentFromDescriptor()[C:autobuild3.2.0frameworksprojectsframeworksrcmxcoreContainer.as:3579]
at mx.core::Container/createComponentsFromDescriptors()[C:autobuild3.2.0frameworksprojectsframeworksrcmxcoreContainer.as:3493]
at mx.containers::ViewStack/instantiateSelectedChild()[C:autobuild3.2.0frameworksprojectsframeworksrcmxcontainersViewStack.as:1140]
at mx.containers::ViewStack/commitProperties()[C:autobuild3.2.0frameworksprojectsframeworksrcmxcontainersViewStack.as:664]
at mx.core::UIComponent/validateProperties()[C:autobuild3.2.0frameworksprojectsframeworksrcmxcoreUIComponent.as:5807]
at mx.managers::LayoutManager/validateProperties()[C:autobuild3.2.0frameworksprojectsframeworksrcmxmanagersLayoutManager.as:539]
at mx.managers::LayoutManager/doPhasedInstantiation()[C:autobuild3.2.0frameworksprojectsframeworksrcmxmanagersLayoutManager.as:689]
at Function/http://adobe.com/AS3/2006/builtin::apply()
at mx.core::UIComponent/callLaterDispatcher2()[C:autobuild3.2.0frameworksprojectsframeworksrcmxcoreUIComponent.as:8628]
at mx.core::UIComponent/callLaterDispatcher()[C:autobuild3.2.0frameworksprojectsframeworksrcmxcoreUIComponent.as:8568]
at flash.utils::Timer/_timerDispatch()
at flash.utils::Timer/tick()
LikeLike
@Bedy – I’ve got to be honest, I’m not really quite sure why this would be. I use this component to access data on another domain from where the video was displayed. I havn’t used it in a while, but I don’t recall having to supply a crossdomain.xml file or anything else.
LikeLike
Hi Doug, gimme a help buddie!
I’m not an expert in Flex, but I followed exactly your tips, but I still can’t see images on the screen.
I created a new project and a component with your code. After that, I created an application file that has an instance of this component (like you wrote!).
But when I run script, nothing shows up on the screen, no images! Debbuging I saw that script is working fine, but nothing appears on the screen… Can you give me a explanation how to set properly imagens to take visible, please?
Thanks!
Rodolfo Reis
LikeLike
Hi Doug –
Could you please tell me, what is HOST in your code?
The thing is that all I got is URL, which returns me MJPEG stream. This url is “http://81.198.212.174/axis-cgi/mjpg/video.cgi?resolution=640×480&compression=30”.
Is that possible to stream it with Flash/Flex in the same approach you have been using?
Thanks in advance.
LikeLike
JUBRvn ltqkswdxhaod, [url=http://ryrbpgwznioq.com/]ryrbpgwznioq[/url], [link=http://kftgehkxanam.com/]kftgehkxanam[/link], http://wzbrxajawlhj.com/
LikeLike
Nice work,
The multipart respsone does contain what the boundary string for the document will be so you could parse that instead of assuming
“–video boundary–”
In my experience “–my boundary” as far more common.
Regards,
Erik
LikeLike
Found the swf file on my axis 213ptz on the same folder of mjpeg:
baseurl/mjpg/video.mjpg
can be:
baseurl/mjpg/video.swf
and it works easy!
LikeLike
Hello,
I installed the component in /src/components, loaded the components using:
xmlns:components=”components.*”
and call the component using
My network camera is visible using this in a browser:
http://127.0.0.1/axis-cgi/mjpg/video.cgi?resolution=640×480
But the flex page is not showing anything.
What am I doing wrong?
Thanks a lot
LikeLike
The problem will only be solved if there’s a possibility of adding a crossdomain.xml file to the camera’s web server OR by making the flash player think it’s coming from there by proxying through a server such that http://myserver.com/crossdomain.xml serves up the file and then having a pass throuh proxy that accesses the camera like http://myserver.com/mycamera.mjpeg
The reason it worked for the author is that the access of the server was to localhost. Flash allows accesses from localhost to sockets without considering this a security violation.
Hope this helps.
LikeLike
I think Bill is exactly correct here. As indicated in the post, I’m fairly new to Flex programming. I never even finished this project, beyond creating the webcamImage component. Thus, I never tried to host this on the web or anything of the sort. It DID work for me. But I think that it’s because it was an AIR application running locally.
Like Bill says, you should be able to use something like Apache’s modproxy to proxy the request to the camera through a domain you can add the crossdomain.xml file to.
Sorry for the general abandonware of this component! I hope some people are able to get some use out of it.
LikeLike
This is an old post, but I just thought I’d let you know that instead of looking for a video boundary to find the end of a jpg, you can just look for the next ending JPG bytes (FF D9) like you are looking for the beginning bytes (FF D8).
The problem I have while using this is speed. I’m not sure if my camera sends back data faster or bigger images than yours, but whenever I have the connection open the GUI freezes, and responds if I close it.
Also, shouldn’t the line
httpRequest += “Host: localhost:80rn”;
be
httpRequest += “Host:” + host + “:” + port”rn”;
?
LikeLike
httpRequest += “Host:” + host + “:” + port + “rn”;
LikeLike
Hi how can y use this in IE. Do you have any example. I try use this in IE and can’t see anything, but if a execute in flash mx works well.
LikeLike
Hi, I wrote this entry more than a year ago and have since pretty much ignored it. However, it still gets a good number of comments on a regular basis. I never expected this. A lot of people seem to have problems with the component. I can probably expand on this, but I want to know how people are using this. What do you need? What do you want? Why mjpeg streams?
And @Andre, I think your question was addressed in earlier comments. Specifically, the component, when served via the web, won’t be able to make requests to hosts other than that which served it. You might be able to use something like a proxy configuration in Apache to forward the request to your camera.
Anyhow people – let me know what you need and I’ll see if I can’t make something a bit more useful.
LikeLike
blu-ray…
Good post and it appears they are moving in the direction you suggest….
LikeLike
The component download link is broken. Is the component still available anywhere?
LikeLike
Better late than never: I’ve fixed the download link.
LikeLike
Thank you for updating the link!
I got it working, even though it’s a bit slow and flickery. Looking for the jpeg end marker, or using the headers Content-Length value might improve speed.
Anyone using a Foscam: the video boundary is “–ipcamera” as in:
–ipcamera
Content-Type: image/jpeg
Content-Length: 38648
As for the flash player security: As long as you run this app from your own pc as opposed to hosting it on a webserver everything is fine. As soon as you publish the swf on a server the webcam will need to provide a valid policy file.
LikeLike
Does this code really works? I have downloaded and tested and no works at all!!
But if it works, good luck!
LikeLike
I’ve tried a version of this and have made it work in a flex ‘AIR’ application which is not bound by crossdomain issues. It also works well on the android š
If anyone ever finds a way to also capture the raw .wav file that some camera’s provide (sound)… let me know
2 classes here – Mjpeg.as and Base64
package
{
import com.dynamicflash.util.Base64;
import flash.display.Loader;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.media.Sound;
import flash.net.Socket;
import flash.utils.ByteArray;
//import mx.utils.Base64Encoder;
//import mx.utils.Base64Decoder;
/**
* This is a class used to view a MJPEG
* @author Josh Chernoff | GFX Complex
*
*/
public class MJPEG extends Loader
{
private var _user:String; //Auth user name
private var _pass:String; //Auth user password
private var _host:String; //host server of stream
private var _port:int; //port of stream
private var _file:String; //Location of MJPEG
private var _start:int = 0; //marker for start of jpg
private var webcamSocket:Socket = new Socket(); //socket connection
private var imageBuffer:ByteArray = new ByteArray(); //image holder
private var outputSnd:Sound = new Sound();
//private var myEncoder:Base64Encoder = new Base64Encoder();
//private var tim = myEncoder.toString();
/**
* Create’s a new instance of the MJPEG class. Note that due a sandbox security problem, unless you can place a crossdomain.xml
* on the host server you will only be able to use this class in your AIR applications.
*
* @example import MJPEG;
* var cam:MJPEG = new MJPEG(“192.168.0.100”, “/img/video.mjpeg”, 80);
* addChild(cam);
*
* @param host:String | Host of the server. Do not include protocol
* @param file:String | Path to the file on the server. Start with a forward slash
* @param port:int | Port of the host server;
* @param user:String | User name for Auth
* @param pass:String | User password for Auth
*/
public function MJPEG (host:String, file:String, port:int = 80, user:String = null, pass:String = null )
{
_host = host;
_file = file;
_port = port;
_user = user;
_pass = pass;
webcamSocket.addEventListener(Event.CONNECT, handleConnect);
webcamSocket.addEventListener(IOErrorEvent.IO_ERROR, unableToConnect);
webcamSocket.addEventListener(ProgressEvent.SOCKET_DATA, handleData);
//outputSnd.addEventListener(SampleDataEvent.SAMPLE_DATA, processSound);
webcamSocket.connect(host, port);
}
private function unableToConnect(e:Event):void{
trace(“error”);
dispatchEvent(new Event(“videoError”));
}
private function handleConnect(e:Event):void
{
// we’re connected send a request
var httpRequest:String = “GET “+_file+” HTTP/1.1rn”;
httpRequest+= “Host: localhost:80rn”;
if(_user != null && _pass != null){
var source:String = String(_user + “:” + _pass);
var auth:String = Base64.encode(source);
httpRequest += “Authorization: Basic ” + auth.toString()+ “rn”; //NOTE THIS MAY NEEED TO BE EDITED TO WORK WITH YOUR CAM
}
httpRequest+=”Connection: keep-alivernrn”;
webcamSocket.writeMultiByte(httpRequest, “us-ascii”);
}
private function handleData(e:ProgressEvent):void {
//trace(“Got Data!” + e);
// get the data that we received.
// append the data to our imageBuffer
webcamSocket.readBytes(imageBuffer, imageBuffer.length);
if (imageBuffer.length 1) {
if(_start == 0){
//Check for start of JPG
for (x; x < imageBuffer.length – 1; x++) {
// get the first two bytes.
imageBuffer.position = x;
imageBuffer.readBytes(startMarker, 0, 2);
//Check for end of JPG
if (startMarker[0] == 255 && startMarker[1] == 216) {
_start = x;
break;
}
}
}
for (x; x 0) {
// Create new data buffer and populate next 3 bytes from data
dataBuffer = new Array();
for (var i:uint = 0; i 0; i++) {
dataBuffer[i] = data.readUnsignedByte();
}
// Convert to data buffer Base64 character positions and
// store in output buffer
outputBuffer[0] = (dataBuffer[0] & 0xfc) >> 2;
outputBuffer[1] = ((dataBuffer[0] & 0x03) <> 4);
outputBuffer[2] = ((dataBuffer[1] & 0x0f) <> 6);
outputBuffer[3] = dataBuffer[2] & 0x3f;
// If data buffer was short (i.e not 3 characters) then set
// end character indexes in data buffer to index of ‘=’ symbol.
// This is necessary because Base64 data is always a multiple of
// 4 bytes and is basses with ‘=’ symbols.
for (var j:uint = dataBuffer.length; j < 3; j++) {
outputBuffer[j + 1] = 64;
}
// Loop through output buffer and add Base64 characters to
// encoded data string for each character.
for (var k:uint = 0; k < outputBuffer.length; k++) {
output += BASE64_CHARS.charAt(outputBuffer[k]);
}
}
// Return encoded data
return output;
}
public static function decode(data:String):String {
// Decode data to ByteArray
var bytes:ByteArray = decodeToByteArray(data);
// Convert to string and return
return bytes.readUTFBytes(bytes.length);
}
public static function decodeToByteArray(data:String):ByteArray {
// Initialise output ByteArray for decoded data
var output:ByteArray = new ByteArray();
// Create data and output buffers
var dataBuffer:Array = new Array(4);
var outputBuffer:Array = new Array(3);
// While there are data bytes left to be processed
for (var i:uint = 0; i < data.length; i += 4) {
// Populate data buffer with position of Base64 characters for
// next 4 bytes from encoded data
for (var j:uint = 0; j < 4 && i + j < data.length; j++) {
dataBuffer[j] = BASE64_CHARS.indexOf(data.charAt(i + j));
}
// Decode data buffer back into bytes
outputBuffer[0] = (dataBuffer[0] <> 4);
outputBuffer[1] = ((dataBuffer[1] & 0x0f) <> 2);
outputBuffer[2] = ((dataBuffer[2] & 0x03) << 6) + dataBuffer[3];
// Add all non-padded bytes in output buffer to decoded data
for (var k:uint = 0; k < outputBuffer.length; k++) {
if (dataBuffer[k+1] == 64) break;
output.writeByte(outputBuffer[k]);
}
}
// Rewind decoded data ByteArray
output.position = 0;
// Return decoded data
return output;
}
public function Base64() {
throw new Error("Base64 class is static container only");
}
}
}
LikeLike
Hmm, where did Tim’s comment go ? (which I got from the “Notify me of followup comments via e-mail”) Even though that code didn’t work, and was severely altered in the process of posting/mailing.
LikeLike
There it is..
LikeLike
myvideo downloader…
[…]Viewing MJPEG Streams in Flex – Alagad Ally[…]…
LikeLike