Peer2Peer with FlashPlayer RTMFP

What is Peer to peer, what can i use it for?

( beta tutorial )



This tutorial was written as I explored the topic so code is built on, in away that allows you to test and understand rather than just a code dump, but obviously it could be more detailed and may improve with time.


Since flash player 10.1 flashplayer has included the capability to provide direct FlashPlayer to FlashPlayer streaming between FlashPlayer nodes ( a signing server is also needed, more on that later ).


In practical terms this means you can broadcast video to a large online meeting without requiring large server capacity, and also easily setup IRC style chat rooms, and perhaps video carousel's to collect all the worst types online together!


To use peer to peer in flash you need a special signing server that is used to negotiate the communication of the group and maintains security sandbox issues, but the actual streaming is effectively player to player - front end!


This technology uses RTMFP ( an adobe protocol ) for direct connections via UDP, some large corporations completely block outgoing UDP traffic, so use may require firewall adjustments.

Signing Server Alternatives


Currently there are two solutions to the signing server.

They are both currently free to experiment with, but Cirrus is beta and you must request a developer key and agree to Adobe's conditions, it is likely to be a paid service in the future, but for large clients it is likely the cost would be justifiable. Cumulus is also in development and licenced under GNU licence, and Cumulus wish to remind us CumulusLib can't be linked with any closed source project.

Cumulus seems to suggest we need a 'Cirrus key' - this needs clarification.

This may help to clarify.From Cumulus on Github

Theoretically you don't have to choose between them, you can load in the path to the services allowing you to switch services, for instance if Adobe change the player and it breaks Cumulus you could switch over ( - need to check this ).

To use Peer2Peer will probably need a dedicated server to install the signing service ( for instance I (JLM) use Gandi dedicated servers but there are lots of providers so shop around ).

Cumulus is C++ based and is cross platform, but probably requires you compile it on the server, most of the heavy work of Cumulus is done by Poco and it is important to get the correct version of Poco for the project to compile. Cirrus requires a Python script that talks with Adobe servers, there is a sqllite database used and it seems it would be easier to install but more complex in the actual setup. So far we have only setup a Cumulus server so lets look at how to use it.

Typically you will need to configure a script to stop and start your service. ( Niel please add info here )

Then when started it should provide information on the 'key'.... a really long number.

INFO  Handshake[43] Flash far id of this cumulus server : 
1111111111111111
NOTE  RTMFPServer[110] RTMFP server starts on 1935 port

Once started you can use it from flash. An example of a connection call would look like
_nc.connect( "rtmfp://100.100.100.100:1935/1111111111111111" );

Where _nc is a NetConnection instance that you maybe familiar with if you have setup video players, 100.100.100.100 would be your internet address, 1935 is the port number you have configured or has been assumed, and 1111111111111111 would be the key a very long number ( or Flash far id ).


First Test


To test the server we need to set up a NetConnection and listen for a connection NetStatusEvent.
package;

// flash imports
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.Lib;
import haxe.Timer;

// connection specific imports
import flash.net.NetConnection;
import flash.events.NetStatusEvent;

class CumulusTest extends Sprite
{
    
    
    public static var _this:                    CumulusTest;
    private var _local:                         Sprite;
    private var _timer:                         Timer;
    private var _nc:                            NetConnection;
    public inline static var _cumulus:         String                      = 'rtmfp://100.100.100.100:1935/1111111111111111';
    
    
    
    private function ready()
    {
        
        _local = Lib.current;
        
        // test we have visuals...
        drawCircle( _local );
        trace( ' Connecting to Cumulus...) ');
        
        _nc = new NetConnection();
        _nc.addEventListener( 
                                NetStatusEvent.NET_STATUS, 
                                function( e: NetStatusEvent )
                                {
                                    
                                    switch ( e.info.code ) 
                                    {
                                        
                                        case "NetConnection.Connect.Success":
                                            trace( '   connection made' );
                                        case "NetConnection.Connect.Failed":
                                            trace( 'CONNECTION FAIL: change your firewall settings or you are using the wrong key - 90 sec timeout' );
                                    }
                                    
                                }
                            );
        _nc.connect( _cumulus );
        
    }
    
    
    
    // initialisation code, waits for stage to be initialized etc... 
    // not relevent to the test, just how I setup my flash.
    static function main(){ _this = new CumulusTest (); } public function new()
    {
        
        super();
        _timer      = new Timer( 40 );
        _timer.run  = isReady;
        
    }
    
    
    private function isReady()
    {
        
        if( Lib.current.stage != null ) { if( Std.is( Lib.current.stage.stageWidth , Int ) ) 
        {
                
                ready();
                _timer.stop();
                
        }}
        
    }
    
    private function drawCircle( mc_: Sprite )
    {
        mc_.graphics.lineStyle( 0, 0xFF000, 0 );
        mc_.graphics.beginFill( 0xFF0000, 1 );
        mc_.graphics.drawCircle( 200, 200, 100 );
    }
    
    
}

The compile script
-swf9 cumulus.swf
-swf-header 1024:760:30:cccccc
-swf-version 10
-main CumulusTest

If your setup correctly and you put the correct string for _cumulus static var you should get confirmation of your connection.

Setting Up A Webcam For Sending

Currently literally posting code as I go... so subject to change...

Rather than put all code in the main class lets create a separate class to handle the webcam output. It's important that we can check our webcam is working so we need to output to a video instance and add to stage.
Rather than output a huge image, we will scale the image.

package;

import flash.display.MovieClip;
import flash.display.Bitmap;
import flash.media.Camera;
import flash.media.Video;
import flash.media.Microphone;
import flash.display.BitmapData;
import flash.net.NetStream;
import flash.events.NetStatusEvent;
import flash.net.NetConnection;


class CameraView
{
    
    
    private var _cam:               Camera;
    private var _mic:               Microphone;
    private var _vid:               Video;
    private var _scope:             MovieClip;
    private var _width:             Float;
    private var _height:            Float;
    private var _nc:                NetConnection;
    private var _ns:                NetStream;
    
    public function getHeight():    Float { return _height; }
    public function getWidth():     Float { return _width;  }
    
    
    public function new( scope_: MovieClip )
    {
        
        _scope  = scope_;
        _cam    = Camera.getCamera();
        _mic    = Microphone.getMicrophone();
        
    }
    
    
    public function send( nc_: NetConnection )
    {
        
        _nc = nc_;
        
        if ( _cam != null )
        {
            
            _cam.setMode( 215, 215, 30 );
            
            _vid                = new Video( _cam.width, _cam.height );
            
            _vid.attachCamera( _cam );
            _scope.addChild( _vid );
            
            var scale           = 215/_cam.width;
            _vid.width          = _cam.width*scale;
            _vid.height         = _cam.height*scale;
            _width              = _vid.width;
            _height             = _vid.height;
            
            _ns = new NetStream( _nc, NetStream.DIRECT_CONNECTIONS );
            
            _ns.addEventListener( NetStatusEvent.NET_STATUS, traceStatus ); 
            _ns.publish( "media" ); 
            _ns.attachAudio( _mic ); 
            _ns.attachCamera( _cam );
            
        } 
        else 
        {
            
            trace("You need a camera.");
            
        }
        
    }
    
    
    private function traceStatus( e: NetStatusEvent )
    {
       trace( ' stream status event  ' );
        if ( e.info.level == "error" ) 
        {
            trace ("Fail");
            return;
        }

        switch( e.info.code ) 
        {
            
            case "NetStream.Play.Start":
                trace( "Stream Success" );
                
            case "NetStream.Buffer.Empty":
                trace( "buffer empty" );
            
        }
        
    }
    
    // maybe useful?
    public function photo(): BitmapData
    {
        var b = new BitmapData( Std.int( _scope.width ), Std.int( _scope.height ) );
        b.draw( _scope );
        return b;
    
    }
    
}

Now in our main class I am going to make sure the connection is setup before the stream is attached and also add a MovieClip to play the Camera output.

package;

// flash imports
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.Lib;
import haxe.Timer;

// connection specific imports
import flash.net.NetConnection;
import flash.events.NetStatusEvent;

// Web cam and set up send stream

class CumulusTest extends Sprite
{
    
    
    public static var _this:                    CumulusTest;
    private var _local:                         Sprite;
    private var _timer:                         Timer;
    private var _nc:                            NetConnection;
    private var _cameraView:                    CameraView;
    private var _sendView:                      MovieClip;
    public inline static var _cumulus:         String                      = 'rtmfp://100.100.100.100:1935/1111111111111111';
    
    
    
    private function ready()
    {
        
        _local = Lib.current;
        
        // test we have visuals...
        //drawCircle( _local );
        //trace( ' Ready :) ');
        
        _sendView   = new MovieClip();
        _sendView.x = 250;
        _sendView.y = 100;
        
        _local.addChild( _sendView );
        _cameraView = new CameraView( _sendView );
        
        _nc     = new NetConnection();
        var camera = _cameraView;
        var nc  = _nc;
        
        _nc.addEventListener( 
                                NetStatusEvent.NET_STATUS, 
                                function( e: NetStatusEvent )
                                {
                                    
                                    switch ( e.info.code ) 
                                    {
                                        
                                        case "NetConnection.Connect.Success":
                                            trace( 'connection made' );
                                            camera.send( nc );
                                            
                                        case "NetConnection.Connect.Failed":
                                            trace( 'CONNECTION FAIL: change your firewall settings or you are using the wrong key - 90 sec timeout' );
                                    }
                                    
                                }
                            );
        _nc.connect( _cumulus );
        
        
        
    }
    
    
    
    // initialisation code, waits for stage to be initialized etc... 
    
    static function main(){ _this = new CumulusTest (); } public function new()
    {
        
        super();
        _timer      = new Timer( 40 );
        _timer.run  = isReady;
        
    }
    
    
    private function isReady()
    {
        
        if( Lib.current.stage != null ) { if( Std.is( Lib.current.stage.stageWidth , Int ) ) 
        {
                
                ready();
                _timer.stop();
                
        }}
        
    }
    
    private function drawCircle( mc_: Sprite )
    {
        mc_.graphics.lineStyle( 0, 0xFF000, 0 );
        mc_.graphics.beginFill( 0xFF0000, 1 );
        mc_.graphics.drawCircle( 200, 200, 100 );
    }
    
    
}

Constructing the Recieve Video View


For receiving the video stream we need to know the senders ID, unfortunately this needs to be provided via a third party service.

We can however trace the netconnection nearID to the screen and then hardcode it into our movie run another
copy and see the video streaming, you can tell it is streaming because of the delay.

Simple Triangle Play Button


Ok before we go any further I we need some simple buttons to start the send or recieve service, for simplicity I won't use any 3rd party tools since I nomally use flash ide for graphics, while other haxe users use swfmill or sam, instead I will create some triangles in code, as it seems like they are suitable for play. As a rollover state I will add a white glow, not really related but fun if you happen to be new to flash.
package;

import flash.display.MovieClip;
import flash.filters.GlowFilter;
import flash.events.MouseEvent;



class TriangleView
{
    
    private var _scope:                 MovieClip;
    private static inline var _r:       Float               = 20;
    
    
    public function new( 
                            scope_:     MovieClip, 
                            col_:       Int,
                            func_:      Void -> Void 
                        )
    {
        
        _scope      = scope_;
        
        _scope.graphics.lineStyle( 0, col_, 0 );
        _scope.graphics.beginFill( col_, 1 );
        var rads    = Math.PI/180;
        var third   = 120;
        
        var theta   = third*rads;
        _scope.graphics.moveTo( _r*Math.sin( theta ), _r*Math.cos( theta) );
        
        theta       = 2*third*rads;
        _scope.graphics.lineTo( _r*Math.sin( theta ), _r*Math.cos( theta ) );
        
        theta       = 3*third*rads;
        _scope.graphics.lineTo( _r*Math.sin( theta ), _r*Math.cos( theta ) );
        
        theta       = third*rads;
        _scope.graphics.lineTo( _r*Math.sin( theta ), _r*Math.cos( theta ) );
        
        _scope.graphics.endFill();
        _scope.rotation -= 90;
        
        _scope.mouseChildren    = false;
        _scope.buttonMode       = true;
        
        _scope.addEventListener( MouseEvent.MOUSE_OVER, over );
        _scope.addEventListener( MouseEvent.MOUSE_OUT, out );
        
        var func = func_;
        _scope.addEventListener( MouseEvent.MOUSE_DOWN, function( e: MouseEvent ){ func(); } );
        
        
    }
    
    
    public function over( e: MouseEvent )
    {
        
        _scope.filters = [new GlowFilter( 0xffffff, 10, 10, 5, 10 )];
        
    }
    
    
    public function out( e: MouseEvent )
    {
        
        _scope.filters = null;
        
    }
    
    
}

Recieve View


Ok so we have buttons. How about the view. Well essentially it is just a stripped down video player, the important aspect is the inclusion of the nearID we want to listen to. We will pass it the net connection.
package;

import flash.display.MovieClip;
import flash.media.Video;
import flash.events.AsyncErrorEvent;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.media.SoundMixer;
import flash.media.SoundTransform;
import flash.net.NetConnection;
import flash.net.NetStream;

class RecieveView
{
    
    private var _scope:             MovieClip;
    private var _nc:                NetConnection;
    private var _ns:                NetStream;
    private var _vid:               Video;
    
    public function new( scope_: MovieClip )
    {
        
        _scope          = scope_;
        _vid            = new Video();
        _vid.smoothing  = true;
        _scope.addChild( _vid );
        
    }
    
    
    public function recieve( nc_: NetConnection )
    {
        
        _nc = nc_;
        connectStream();
        _ns.play( "media" );
        
    }
    
    
    private function netStatusHandler( event: NetStatusEvent )
    {
        
        trace('Video Connected');
        
    }
    
    
    private function connectStream()
    {
        // You need to get the senders current nearID, via a secondary service, 
        // here I am using lots of 2's but you get the idea
        _ns                     = new NetStream( _nc, '222222222222' );
        
        _ns.bufferTime          = 3;
        
        _ns.addEventListener( NetStatusEvent.NET_STATUS,            netStatusHandler      );
        _ns.addEventListener( AsyncErrorEvent.ASYNC_ERROR,          asyncErrorHandler      );
        _nc.addEventListener( SecurityErrorEvent.SECURITY_ERROR,    securityErrorHandler );
        
        _vid.attachNetStream( _ns );
        
    }
    
    
    private function securityErrorHandler( event: SecurityErrorEvent )
    {
        
        //trace("securityErrorHandler: " + event);
        
    }
    
    
    private function asyncErrorHandler( event: AsyncErrorEvent )
    {
        
        // ignore AsyncErrorEvent events.
        
    }
    
    
}

Now we need to put it all together so we can either use one movie to send and another to receive the web cam video. One important note is that we need to trace the netconnection nearID and also a small hack allows us to
select an copy the haxe trace.

Final version of the main class - CumulusTest


package;

// flash imports
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.Lib;
import haxe.Timer;

// connection specific imports
import flash.net.NetConnection;
import flash.events.NetStatusEvent;

// Web cam and set up send stream

class CumlusTest extends Sprite
{
    
    
    public static var _this:                    CumulusTest;
    private var _local:                         Sprite;
    private var _timer:                         Timer;
    private var _nc:                            NetConnection;
    private var _cameraView:                    CameraView;
    private var _recieveView:                   RecieveView;
    private var _sendMc:                        MovieClip;
    private var _recieveMc:                     MovieClip;
    private var _sendBtn:                       MovieClip;
    private var _recieveBtn:                    MovieClip;
    
    public inline static var _cumulus:         String                      = 'rtmfp://100.100.100.100:1935/1111111111';
    
    private function ready()
    {
        
        _local = Lib.current;
        
        _nc     = new NetConnection();
        var nc = _nc;
        _nc.addEventListener( 
                                NetStatusEvent.NET_STATUS, 
                                function( e: NetStatusEvent )
                                {
                                    
                                    switch ( e.info.code ) 
                                    {
                                        
                                        case "NetConnection.Connect.Success":
                                            trace( 'connection made');
                                            trace( ' near id ' + nc.nearID );
                                            trace( ' far id ' + nc.farID );
                                            
                                            
                                        case "NetConnection.Connect.Failed":
                                            trace( 'CONNECTION FAIL: change your firewall settings or you are using the wrong key - 90 sec timeout' );
                                    }
                                    
                                }
                            );
                            
        _nc.connect( _cumulus, 100 );
        
        _sendBtn                    = new MovieClip();
        _sendBtn.x                  = 250;
        _sendBtn.y                  = 100 - 60;
        
        _local.addChild( _sendBtn );
        
        var blue                    = 0x0000ff;
        var _tri                    = new TriangleView( _sendBtn, blue, sendVideo );
        
        
        _recieveBtn                 = new MovieClip();
        _recieveBtn.rotation        += 180;
        _recieveBtn.x               = 300;
        _recieveBtn.y               = 100 - 60;
        
        _local.addChild( _recieveBtn );
        
        var green                   = 0x00ff000;
        var _tri                    = new TriangleView( _recieveBtn, green, recieveVideo );
        
        // these lines allow us to select the nearID that is traced out 
        flash.Boot.getTrace().mouseEnabled = true;
        flash.Boot.getTrace().selectable   = true;
    }
    
    private function sendVideo()
    {
        
        _sendBtn.visible            = false;
        _recieveBtn.visible         = false;
        _sendMc                     = new MovieClip();
        _sendMc.x                   = 250;
        _sendMc.y                   = 100;
        
        _local.addChild( _sendMc );
        _cameraView                 = new CameraView( _sendMc );
        _cameraView.send( _nc );
        
    }
    
    private function recieveVideo()
    {
        
        _sendBtn.visible            = false;
        _recieveBtn.visible         = false;
        _recieveMc                  = new MovieClip();
        _recieveMc.x                = 250;
        _recieveMc.y                = 100;
        
        _local.addChild( _recieveMc );
        _recieveView                = new RecieveView( _recieveMc );
        _recieveView.recieve( _nc );
        
    }
    
    // initialisation code, waits for stage to be initialized etc... 
    
    static function main(){ _this = new CumulusTest (); } public function new()
    {
        
        super();
        _timer      = new Timer( 40 );
        _timer.run  = isReady;
        
    }
    
    
    private function isReady()
    {
        
        if( Lib.current.stage != null ) { if( Std.is( Lib.current.stage.stageWidth , Int ) ) 
        {
                
                ready();
                _timer.stop();
                
        }}
        
    }
    
    
}

I am really not sure that sound has been setup but this should provide basic flash2flash video, from one web camera to another persons machine, I have only tried this from my machine, and this is still my first attempt so there is lots to improve but hopefully it will give you a headstart.

version #19930, modified 2014-02-06 19:02:46 by frankendres