Wednesday, 6 January 2010

Peer to peer video and audio streaming with Flex

Hi Readers,

This going to be a quick blogpost about how to build a simple video and audio streaming app in Flex. All the fame is for the Adobe Stratus service, and for Tom Krcha, a very great Adobe tech evangelist: http://www.flashrealtime.com/basics-of-p2p-in-flash/ (this is the original article.) Most of my code is from there! Great thanks for your excellent tutorial.

So, first take a look at the Stratus service: http://labs.adobe.com/technologies/stratus/ It allows to your Flash app to create P2P connection between clients. Cool, isn't it?

Our app will open the default camera and microphone streams and transfer to any other client we share our peerID with. I succeeded to open 3 additional connection. That's already two more than Skype allows. Of course, it's not that good.



Ok ok, maybe it's better to try it out: Multivideo.swf
- Allow your camera and microphone to connect
- Share your peerID with your partners using the app (your peerID is in the first text box)
- Add new streams by adding other contacts' peerIDs: copy their peerID into the 2nd textbox and click on [Add contact]
(On some systems sometimes firewall blocks the stream.)

Let's do it than. We need some UIComponent. A base video display and some textbox and a button:
<mx:HBox id="video_stack" top="10" left="10">
<mx:VBox>
<mx:VideoDisplay id="my_video_display" width="320" height="240"/>
<mx:HBox>
<mx:TextInput width="320" id="farPeerId_text" text="Your Peer ID is loading..."/>
</mx:HBox>
<mx:HBox id="add_contact_container" visible="false">
<mx:TextInput id="contact_peer_id_text" width="200"/>
<mx:Button label="Add contact" click="{addContact();}"/>
</mx:HBox>
</mx:VBox>
</mx:HBox>

HBox allows us just pushing the new stream UI elements into the horizontal queue.
Using Stratus we have to register for a Stratus API key. We will get an ID and the url:
private var rtmfpServer:String = 'rtmfp://stratus.adobe.com/cbd2224f9a56771b3d4d05c3-bd9b549abca2';

Than make some vars:
private var nc:NetConnection;
private var sendNS:NetStream;
private var neerPeerID:String;

private var cam:Camera;
private var mic:Microphone;

When the application successfully loaded, we can init our camera, microphone and the net connection for outgoing streams:
private function init():void {
initCamera();
initNetConnection();
}

Taking the cam/mic is quite straightforward:
private function initCamera():void {
if (Camera.names.length > 0) {
cam = Camera.getCamera();
my_video_display.attachCamera(cam);
}

if (Microphone.names.length > 0) {
mic = Microphone.getMicrophone();
}
}

I said so. Than the net connection to the stratus server through RTMFP protocol:
private function initNetConnection():void {
nc = new NetConnection();
nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusEvent);
nc.connect(rtmfpServer);
}

When the net connection is ready, we can establish the sending stream:
private function netStatusEvent(event:NetStatusEvent):void {
trace('NetConnection status event (1): ' + event.info.code);
if (event.info.code == 'NetConnection.Connect.Success') {
neerPeerID = nc.nearID;
farPeerId_text.text = neerPeerID;
initSendNetStream();
add_contact_container.visible = true;
}
}

This is the point we've got out peerID we can share. wOOt! On setting the provider stream we can attach out camera to the stream. And we can define a client object accepts the connection (return true;):
private function initSendNetStream():void {
sendNS = new NetStream(nc, NetStream.DIRECT_CONNECTIONS);
sendNS.addEventListener(NetStatusEvent.NET_STATUS, netStatusEvent);

var clientObject:Object = new Object();
clientObject.onPeerConnect = function(ns:NetStream):Boolean {return true;}

sendNS.client = clientObject;
sendNS.attachCamera(cam);
sendNS.attachAudio(mic);
sendNS.publish('video');
}

So far we have a working camera, microphone, and the stream is already on. Now prepare for accepting others stream:
private function addContact():void {
var nc2:NetConnection = new NetConnection();
nc2.addEventListener(NetStatusEvent.NET_STATUS, function (event:NetStatusEvent):void {
trace('NetConnection status event (2): ' + event.info.code);
var receiveNS:NetStream = new NetStream(nc2, contact_peer_id_text.text);
receiveNS.addEventListener(NetStatusEvent.NET_STATUS, netStatusEvent);
receiveNS.play('video');

var video:Video = new Video();
video.attachNetStream(receiveNS);

var uic:UIComponent = new UIComponent();
uic.width = 320;
uic.height = 240;
uic.addChild(video);
video_stack.addChild(uic);

contact_peer_id_text.text = '';
});
nc2.connect(rtmfpServer);
}

When we get a new stream, we create a new video object and add onto the stage.
I know, it's far far basic, but it's good for starting a tool like this.

Download the source code.

Bests,
Peter