Sharing webcams over Red5 and Flex/ActionScript

Comments

avatar john

Very interesting application. If this could be secure it would be awesome. I will try it myself.

I found it extremely difficult to find out the simplest and most explanatory way of creating a little flash app that shares microphones and webcams between only 2 people.  I managed to get it working in the end with a lot of fighting with adobe’s livedocs, so let’s have a look at it.

There’s 3 things you need, a HTML webpage, a Flex application, and a Red5 application.  I would use Flash Media Server but the barrier to entry is a little bit bigger.  I was told to enter a required telephone number on the registration page so just decided it wasn’t worth it.

The Red5 application sounds the most daunting part to me, having not played around much with Java for a long time.  For this very simple example though it’s actually the easiest piece once you understand what Red5 is doing under the hood (and have Red5 installed).  I also found it a great deal of help setting up an Ant build file which ended up managing compilation and distribution of both the Java and Flex projects.

Red5 keeps track of all the published streams from any connected clients and will let each of those clients subscribe to any other connected stream.  So what you actually end up with is just the stub of an ApplicationAdapter along with some boiler plate Red5 application configuration.

package com.talk;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;

public class Application extends ApplicationAdapter {
    private static final Log log = LogFactory.getLog(Application.class);

    public boolean connect(IConnection conn, IScope scope, Object[] params) {
        log.debug("Talk connect for: " + conn.getScope().getContextPath());
        return super.connect(conn, scope, params);
    }
    public void disconnect(IConnection conn, IScope scope) {
        log.debug("Talk disconnect for: " + conn.getScope().getContextPath());
        super.disconnect(conn, scope);
    }
}

The rest of the code is available on my github profile.

Now that we’ve got the Red5 app done we can do the Flex / ActionScript stuff.  I don’t want anything fancy and just want it to work, so am going to create a UIComponent called VideoScreen and make that the only component in the application, and position everything absolutely from within the ActionScript.

<!-- talk.mxml -->
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:talk="talk.*" layout="absolute">
  <talk:VideoScreen />
</mx:Application>

// talk/VideoScreen.as
package talk {
    import mx.core.UIComponent;
    public class VideoScreen extends UIComponent {
        public function VideoScreen():void {
            super();
        }
    }
}

From here we can start requesting for the camera and microphone, and put some video panes in there as well as hooking up the NetConnection and NetStreams.  The full code for VideoScreen.as is also on github.  I’m not going to paste the full code here, but here’s a few choice cuts from it.

As discussed on actionscript.org you can mirror a video by altering it’s scaleX property.  This will move the top left pixel further left, so you have to add some extra pixels onto the x property to compensate and get it into the position you want.  We do this for the overlayed video of yourself since it’s far more intuitive to see a mirrors picture of yourself than it is to see a flipped version.

video.scaleX = -1 * video.scaleX;

It took me a while to realise, but you can’t publish and play different handles on the same NetStream.  So what I’ve done here is used 2 different handles “me” and “you” and published them on NetStreams “stream” and “receiveStream” depending on the flashvar “who”.  The Red5 rtmp server location is also sent as a flashvar that’s used in this code snippet.

private function connectCamera():void {
    // ... other camera setup here that I've cut out
    nc.addEventListener(NetStatusEvent.NET_STATUS,netStatusHandler);
    var rtmp:String = root.loaderInfo.parameters.rtmp;
    nc.connect(rtmp);
}
private function netStatusHandler(event:NetStatusEvent):void {
    if(event.info.code=="NetConnection.Connect.Success") {
        var who:String = root.loaderInfo.parameters.who;
        var opposite:String = who == "me" ? "you" : "me";
        stream = new NetStream(nc);
        stream.attachCamera(camera);
        stream.attachAudio(mic);
        stream.publish(who, 'live');
        receiveStream = new NetStream(nc);
        receiveStream.play(opposite);
        videoReceived.attachNetStream(receiveStream);
    } else if(event.info.code=="NetStream.Publish.BadName") {
       //someone's already on that stream name
    }
}

It’s weird to realise, but that’s about it!  The reason why that is is again down to what’s going on in the background of Red5 that I mentioned before.  Depending on this flashvar “who” the NetStreams “stream” and “receiveStream” end up publishing to and playing one another like magic.  No server logic required.

This obviously isn’t ideal, since someone’s always going to be able to eavesdrop on your webcam, impersonate you, or even end up taking up both of the 2 stream handlers that we’ve specified here leaving your little app unusable.  We’ll need some session based identifiers and the possibility of restricting connections to get it working a little nicer, but I’ll leave that up to you.  You can also keep an eye on the github repository for any new code based on this example project.