One of my students in my live web class is developing an interesting application that sends screenshots to other people. I put together some sample code to help him along and thought this would be of general interest.
Using the Flash Media Server with Remote Shared Objects this can be built. Here is a walk through of the code:
First of all, this uses the JPGEncoder class from the AS3CoreLib so you probably want to grab that and import it.
import com.adobe.images.JPGEncoder;
This example uses a SharedObject, a NetConnection and NetStreams for sending the video as well as the screen shots. Once the NetConnection is established, the SharedObject can be setup:
// Listener for connection private function connectionHandler (e:NetStatusEvent):void { // If we are connected if (e.info.code == "NetConnection.Connect.Success") { // Set up the shared object // We'll call it SimpleSO, pass in the app url and not make it persistent sharedObject = SharedObject.getRemote("SimpleSO",nc.uri,false); // Tell the shared object to call methods in this class if requested sharedObject.client = this; // Add a listener for when shared object is changed sharedObject.addEventListener (SyncEvent.SYNC,syncEventCallBack); // Connect the shared object to our netConnection sharedObject.connect(nc); // All of the video streaming setup doEverything(); // Register mouseclicks, how we'll determine when to send a frame stage.addEventListener(MouseEvent.MOUSE_DOWN, saveFrame); } }
Here is the method that is called when the mouse is clicked. It creates a bitmapdata object, encodes as a JPEG and sends that as a bytearray through the shared object:
private function saveFrame(e:MouseEvent):void { // Save the frame to the bitmapdata object var bmpd:BitmapData = new BitmapData(320,240); bmpd.draw(videoIn1); // First encode as JPEG so it is smaller var jpgEncoder:JPGEncoder = new JPGEncoder(100); var jpgByteArray:ByteArray = jpgEncoder.encode(bmpd); // Send it via the shared object sharedObject.send("newBitmap",jpgByteArray); }
The SharedObject.send method calls the function “newBitmap” on everyone who is connected and passes in the jpgByteArray. The newBitmap function uses the Loader to uncompress the JPG and when it is done calls “gotBitmapData”:
public function newBitmap(jpgByteArray:ByteArray):void { var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, gotBitmapData) loader.loadBytes(jpgByteArray); }
gotBitmap data just creates a regular Bitmap and displays it:
private function gotBitmapData(e:Event):void { var decodedBitmapData:BitmapData = Bitmap(e.target.content).bitmapData // Create the bitmap image var bmp:Bitmap = new Bitmap(decodedBitmapData); // Add it to the stage bmp.x = 0; bmp.y = 240; addChild(bmp); }
Here is the full AS3 class (it could be improved but it works):
package { // Import JPEGEncoder Class from: http://code.google.com/p/as3corelib/ import com.adobe.images.JPGEncoder; import flash.display.Sprite; import flash.display.MovieClip; import flash.events.NetStatusEvent; import flash.net.NetConnection; import flash.net.NetStream; import flash.media.Camera; import flash.media.Microphone; import flash.media.Video; import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.MouseEvent; import flash.utils.ByteArray; import flash.net.SharedObject; import flash.events.SyncEvent; import flash.events.Event; import flash.display.Loader; public class VideoCapture extends Sprite { // Shared Object for communication private var sharedObject:SharedObject; // Overall NetConnection for communicating with FMS private var nc:NetConnection; // RTMP URL, same as directory on FMS private var rtmpURL:String = "rtmp://xxxx/webcam"; // NetStreams for each stream private var netStreamOut:NetStream; private var netStreamIn1:NetStream; // Camera private var camera:Camera; // Microphone private var microphone:Microphone; // My Video private var videoOut:Video; // Video Components private var videoIn1:Video; // Stream Names private var outStream:String; private var inStream1:String; public function VideoCapture() { // Construct NetConnection and connect to server nc = new NetConnection(); nc.connect(rtmpURL); // Add a listener for connection nc.addEventListener (NetStatusEvent.NET_STATUS,connectionHandler); } // Listener for connection private function connectionHandler (e:NetStatusEvent):void { // If we are connected if (e.info.code == "NetConnection.Connect.Success") { // Set up the shared object // We'll call it SimpleSO, pass in the app url and not make it persistent sharedObject = SharedObject.getRemote("SimpleSO",nc.uri,false); // Tell the shared object to call methods in this class if requested sharedObject.client = this; // Connect the shared object to our netConnection sharedObject.connect(nc); // All of the video streaming doEverything(); // Register mouseclicks, how we'll determine when to send a frame stage.addEventListener(MouseEvent.MOUSE_DOWN, saveFrame); } } // Gets the results from the server private function doEverything():void { // Name of streams outStream="one"; inStream1="one"; // Setup the camera camera = Camera.getCamera(); // setup the microphone microphone = Microphone.getMicrophone(); // Video components videoOut = new Video(); videoIn1 = new Video(); // Set positions videoOut.x = 0; videoOut.y = 0; videoIn1.x = 320; videoIn1.y = 0; // Add them to the screen addChild(videoOut); addChild(videoIn1); // Publish our stream netStreamOut = new NetStream(nc); netStreamOut.attachAudio(microphone); netStreamOut.attachCamera(camera); netStreamOut.publish(outStream, "live"); // Attach camera to our video videoOut.attachCamera(camera); //Play incoming streamed video netStreamIn1 = new NetStream(nc); videoIn1.attachNetStream(netStreamIn1); netStreamIn1.play(inStream1); } private function saveFrame(e:MouseEvent):void { // Save the frame to the bitmapdata object var bmpd:BitmapData = new BitmapData(320,240); bmpd.draw(videoIn1); // First encode as JPEG so it is smaller var jpgEncoder:JPGEncoder = new JPGEncoder(100); var jpgByteArray:ByteArray = jpgEncoder.encode(bmpd); // Send it via the shared object sharedObject.send("newBitmap",jpgByteArray); } public function newBitmap(jpgByteArray:ByteArray):void { var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, gotBitmapData) loader.loadBytes(jpgByteArray); } private function gotBitmapData(e:Event):void { var decodedBitmapData:BitmapData = Bitmap(e.target.content).bitmapData // Create the bitmap image var bmp:Bitmap = new Bitmap(decodedBitmapData); // Add it to the stage bmp.x = 0; bmp.y = 240; addChild(bmp); } } }
it’s VERY important to note that when sending ByteArray over a SharedObject like that, you should first do a: jpgByteArray.compress();
you literally can shave the transfer time from about 20 seconds to less than 1 second depending on the image dimensions/quality.
The difference would not be significant. Perhaps if the ByteArray was not already encoded as a JPEG which is compressed it would make a big difference but since in my example the data is already JPEG encoded doing a compress on the byte array wouldn’t change the size much under normal circumstances.
The best way to make the data smaller in the above example is the modify the:
var jpgEncoder:JPGEncoder = new JPGEncoder(100);
line and choose a smaller number. 100 represents full quality, 50 would be 50% and so on.
Hi,
Can you tell me what you have going on the server-side in the webcam application?
Thanks,
Apurva
Hi Apurva,
I don’t remember exactly but I think this doesn’t require anything more than a default application (directory) on FMS.
-shawn