├── .gitignore ├── demos ├── rtmfp.swf ├── chat │ ├── chat.css │ ├── index.html │ └── chat.js └── common-js │ ├── rtmfp.js │ ├── jquery.json-2.2.min.js │ └── swfobject.js ├── test.htm ├── src └── com │ └── h3xstream │ └── rtmfp │ ├── Logger.as │ ├── Browser.as │ ├── MainSprite.as │ ├── Peer.as │ └── RtmfpConnection.as ├── gh-pages └── styles.css ├── rtmfp-api.as3proj └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | lib/ 3 | bin/ -------------------------------------------------------------------------------- /demos/rtmfp.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h3xstream/rtmfp-api/HEAD/demos/rtmfp.swf -------------------------------------------------------------------------------- /test.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test page 4 | 5 | 6 | 14 | 15 | 16 | 17 | Reload 18 | 19 |
20 |

Could not load swf

21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /src/com/h3xstream/rtmfp/Logger.as: -------------------------------------------------------------------------------- 1 | package com.h3xstream.rtmfp 2 | { 3 | /** 4 | * trace() could have been use to debug the flash app. 5 | * Instead console.info() method is trigger to integrate nicely Chrome and Firefox (Firebug). 6 | */ 7 | public class Logger 8 | { 9 | //Debug should disable in release version (can be override at runtime via flashvars) 10 | public static var DEBUG:Boolean = true; 11 | 12 | /** 13 | * Redirect log to browser console (works in firefox (firebug) and chrome) 14 | * @param message Information to log 15 | */ 16 | public static function log(message:String):void { 17 | if (DEBUG) { 18 | Browser.jsCall("console.info",message); 19 | } 20 | } 21 | 22 | public static function error(message:String,e:Error=null):void { 23 | if (DEBUG) { 24 | var extra:String = e!=null?"\nerrorID:"+e.errorID+"\nmessage:"+e.message+"\nstacktrace:"+e.getStackTrace():""; 25 | Browser.jsCall("console.error","["+message+"]"+extra); 26 | } 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/com/h3xstream/rtmfp/Browser.as: -------------------------------------------------------------------------------- 1 | package com.h3xstream.rtmfp 2 | { 3 | import flash.external.ExternalInterface; 4 | 5 | /** 6 | * This class wrap the interaction with the browser. 7 | * Additionnal escaping is done when calling Javascript function due to a 8 | * flash vulnerability that can lead to XSS exploitation. 9 | */ 10 | public class Browser 11 | { 12 | /** 13 | * Call a Javascript function load in the broswer. 14 | * 15 | * @param callback 16 | * @param ... args 17 | * @return Result of the function call 18 | */ 19 | public static function jsCall(callback:String, ... args):* { 20 | //DO NOT use logging in this method (to avoid recursive call) 21 | for (var arg:String in args) { 22 | arg = escapeFix(arg); 23 | } 24 | args.unshift(callback); 25 | return ExternalInterface.call.apply(null, args); 26 | } 27 | 28 | /** 29 | * Some extra escaping is need to avoid flash bug/vulnerability. 30 | * @param data Parameter to escape 31 | * @return 32 | */ 33 | private static function escapeFix(data:String):String { 34 | return data.split("\\").join("\\\\"); 35 | } 36 | 37 | public static function addCallback(functionName:String,closure:Function):void { 38 | ExternalInterface.addCallback(functionName,closure); 39 | } 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /gh-pages/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body { 7 | background: #222; 8 | color: #333; 9 | font-family: serif; 10 | } 11 | 12 | #main { 13 | background: white; 14 | width: 700px; 15 | margin: 0 auto; 16 | border-right: 2px solid black; 17 | border-bottom: 2px solid black; 18 | 19 | border-bottom-left-radius: 10px 5px; 20 | border-bottom-right-radius: 10px 5px; 21 | } 22 | 23 | #header { 24 | border-bottom: 1px solid #aaa; 25 | background: #eee; 26 | padding-left: 10px; 27 | margin-bottom: 10px; 28 | 29 | border-bottom-left-radius: 10px 5px; 30 | border-bottom-right-radius: 10px 5px; 31 | } 32 | 33 | 34 | 35 | #content { 36 | padding: 0 20px 0 10px; 37 | } 38 | 39 | #footer { 40 | font-family: helvetica; 41 | font-size:0.9em; 42 | border-top: 1px solid #aaa; 43 | background: #eee; 44 | padding: 10px; 45 | text-align: center; 46 | margin-top: 15px; 47 | 48 | border-top-left-radius: 10px 5px; 49 | border-top-right-radius: 10px 5px; 50 | } 51 | 52 | 53 | a { 54 | color: #333; 55 | } 56 | 57 | /** Titles **/ 58 | 59 | #header h1 { 60 | font-family: georgia; 61 | color: #aaa; 62 | letter-spacing: -3px; 63 | font-size: 35px; 64 | display:inline; 65 | } 66 | 67 | #subtitle { 68 | font-style:italic; 69 | color: #777; 70 | font-size: 18px; 71 | } 72 | 73 | h2 { 74 | border-bottom: 1px dotted #aaa; 75 | margin-bottom: 10px; 76 | margin-top: 15px; 77 | } 78 | 79 | p, ul { 80 | padding: 0 0 4px 10px; 81 | line-height: 17px; 82 | font-family: verdana, arial, sans-serif; 83 | font-size: 12px; 84 | } 85 | 86 | ul { 87 | margin-left:20px; 88 | } 89 | 90 | 91 | /** Code section **/ 92 | .syntaxhighlighter,.line { 93 | border: 1px solid #aaa; 94 | font-size:12px !important; 95 | } -------------------------------------------------------------------------------- /src/com/h3xstream/rtmfp/MainSprite.as: -------------------------------------------------------------------------------- 1 | package com.h3xstream.rtmfp 2 | { 3 | import flash.display.Sprite; 4 | //import flash.external.ExternalInterface; 5 | import flash.system.Security; 6 | 7 | /** 8 | * Main class initialize the different classes. 9 | */ 10 | public class MainSprite extends Sprite 11 | { 12 | 13 | private var rtmfpUrl:String = ""; //Should be similar to "rtmfp://p2p.rtmfp.net/API_KEY/" 14 | private var domain:String = "*"; 15 | 16 | private var rtmfp:RtmfpConnection = null; 17 | 18 | public function MainSprite():void { 19 | try { 20 | //Load flashvars parameters 21 | var params:Object = this.loaderInfo.parameters; 22 | if(params['DEBUG'] !== undefined) 23 | Logger.DEBUG = (params['DEBUG'] == 'true'); 24 | if(params['rtmfpUrl'] !== undefined) 25 | rtmfpUrl = params['rtmfpUrl']; 26 | 27 | this.rtmfp = new RtmfpConnection(); 28 | 29 | if(params['onMessageRecvCall'] !== undefined) 30 | rtmfp.onMessageRecvCall = params['onMessageRecvCall']; 31 | if(params['onPeerIdRecvCall'] !== undefined) 32 | rtmfp.onPeerIdRecvCall = params['onPeerIdRecvCall']; 33 | if(params['onPeerConnectCall'] !== undefined) 34 | rtmfp.onPeerConnectCall = params['onPeerConnectCall']; 35 | if(params['onPeerDisconnectCall'] !== undefined) 36 | rtmfp.onPeerDisconnectCall = params['onPeerDisconnectCall']; 37 | 38 | Logger.log("rtmfp-api version 1.0"); 39 | 40 | Security.allowDomain(domain); 41 | 42 | rtmfp.initServerConn(rtmfpUrl); 43 | 44 | initCallbacks(); 45 | 46 | } 47 | catch (e:Error) { 48 | Logger.error('Could not initialise flash app.',e); 49 | } 50 | } 51 | 52 | private function initCallbacks():void { 53 | //Make the method available to the browser (Incoming calls) 54 | Browser.addCallback("connectToPeer", rtmfp.connectToPeer); 55 | Browser.addCallback("send", rtmfp.send); 56 | } 57 | 58 | 59 | 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /demos/chat/chat.css: -------------------------------------------------------------------------------- 1 | body,html { 2 | font-family: Helvetica, Geneva, Arial, sans-serif; 3 | font-size: 1em; 4 | font-style: normal; 5 | font-weight: normal; 6 | margin:0; 7 | padding:0; 8 | color:#000; 9 | background:#E7DEDE; 10 | } 11 | 12 | /** Structure **/ 13 | 14 | #container { 15 | width:750px; 16 | margin:0 auto; 17 | background:#fff; 18 | padding:15px; 19 | } 20 | #header { 21 | padding:5px 10px; 22 | background:#524A42; 23 | } 24 | 25 | #main { 26 | padding:10px; 27 | } 28 | 29 | #sidebar { 30 | float:right; 31 | width:200px; 32 | margin-bottom:4px; 33 | padding:10px; 34 | background:#F6F9FB; 35 | border:2px solid #E1E1E1; 36 | } 37 | #footer { 38 | clear:both; 39 | padding:5px 10px; 40 | background:#948C7B; 41 | } 42 | 43 | #divMessages { 44 | overflow:auto; 45 | min-height:200px; 46 | max-height:300px; 47 | } 48 | 49 | 50 | /** Text **/ 51 | 52 | #title, #room_title { 53 | font-size:22px; 54 | color:#fff; 55 | } 56 | 57 | 58 | #ulListPeers { 59 | margin:0px; 60 | font-weight:bold; 61 | } 62 | 63 | #txtInviteUrl { 64 | color:#F00; 65 | border:1px dashed #F00; 66 | } 67 | #txtInvite { 68 | color:#F00; 69 | font-weight: bold; 70 | } 71 | 72 | /** **/ 73 | 74 | .dialog { 75 | position:absolute; 76 | top:100px; 77 | left: 40%; 78 | border: 3px solid #948C7B; 79 | background-color: #FFF; 80 | z-index:2601; 81 | padding:6px; 82 | 83 | color:#555; 84 | font-size:14px; 85 | } 86 | 87 | #overlay { 88 | background-color:#000000; 89 | opacity:0.6; 90 | height:100%; 91 | left:0; 92 | top:0; 93 | position:absolute; 94 | width:100%; 95 | z-index:2600; 96 | } 97 | 98 | /** Form **/ 99 | 100 | #txtMessage, #btnSendMessage { 101 | height:35px; 102 | } 103 | 104 | #txtMessage { 105 | border:0px; 106 | width:400px; 107 | } 108 | 109 | #txtInviteUrl { 110 | width:500px; 111 | } 112 | 113 | #btnSendMessage { 114 | font-weight: bold; 115 | } -------------------------------------------------------------------------------- /demos/common-js/rtmfp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper use to communicate with the hidden flash applet. 3 | */ 4 | Rtmfp = function(pathSwf,config,callbacks) { 5 | var pub = {}; 6 | 7 | var idSwfObject = "rtmfp"; 8 | var refSwfObject = null; 9 | 10 | function init() { 11 | $("body").append("
"); 12 | 13 | var flashvars = config; 14 | 15 | //Transform function to Global callable name 16 | flashvars['onPeerIdRecvCall'] = Rtmfp.Callbacks.create('onPeerIdRecv',callbacks['onPeerIdRecvCall']); 17 | flashvars['onPeerConnectCall'] = Rtmfp.Callbacks.create('onPeerConnect',callbacks['onPeerConnectCall']); 18 | flashvars['onPeerDisconnectCall'] = Rtmfp.Callbacks.create('onPeerDisconnect',callbacks['onPeerDisconnectCall']); 19 | 20 | flashvars['onMessageRecvCall'] = Rtmfp.Callbacks.create('onMessageRecv', 21 | function(peerId,message) { 22 | var idx = message.indexOf("|"); 23 | 24 | var cmd = message.slice(0,idx); 25 | var data = $.evalJSON(message.slice(idx+1)); //Unserialiaze the object 26 | 27 | callbacks['onMessageRecvCall'].call(null,peerId,cmd,data); 28 | } 29 | ); 30 | 31 | swfobject.embedSWF(pathSwf,idSwfObject, "0", "0", "10.0.0", 32 | "expressInstall.swf",flashvars , {}, {}, 33 | function (res) { 34 | if(res.success) { 35 | refSwfObject = document.getElementById(idSwfObject); 36 | } 37 | }); 38 | } 39 | 40 | pub.connectToPeer = function (peerId) { 41 | peerId = peerId.split("\\").join("\\\\"); 42 | refSwfObject.connectToPeer(peerId); 43 | } 44 | 45 | pub.send = function(command,object) { 46 | var data = $.toJSON(object); //Serialiaze the object 47 | 48 | command = command.split("\\").join("\\\\"); 49 | data = data.split("\\").join("\\\\"); 50 | 51 | refSwfObject.send(command+"|"+data); 52 | } 53 | 54 | init(); 55 | 56 | return pub; 57 | } 58 | 59 | Rtmfp.Callbacks = function() { 60 | var pub = {}; 61 | 62 | /** 63 | * Wrap the function in another one wich is store in the "Rtmfp.Callbacks" scope. 64 | */ 65 | pub.create = function(methodName,funct) { 66 | Rtmfp.Callbacks[methodName] = function() { 67 | return funct.apply(null, arguments); 68 | }; 69 | return 'Rtmfp.Callbacks.'+methodName; 70 | } 71 | 72 | return pub; 73 | }(); -------------------------------------------------------------------------------- /demos/common-js/jquery.json-2.2.min.js: -------------------------------------------------------------------------------- 1 | 2 | (function($){$.toJSON=function(o) 3 | {if(typeof(JSON)=='object'&&JSON.stringify) 4 | return JSON.stringify(o);var type=typeof(o);if(o===null) 5 | return"null";if(type=="undefined") 6 | return undefined;if(type=="number"||type=="boolean") 7 | return o+"";if(type=="string") 8 | return $.quoteString(o);if(type=='object') 9 | {if(typeof o.toJSON=="function") 10 | return $.toJSON(o.toJSON());if(o.constructor===Date) 11 | {var month=o.getUTCMonth()+1;if(month<10)month='0'+month;var day=o.getUTCDate();if(day<10)day='0'+day;var year=o.getUTCFullYear();var hours=o.getUTCHours();if(hours<10)hours='0'+hours;var minutes=o.getUTCMinutes();if(minutes<10)minutes='0'+minutes;var seconds=o.getUTCSeconds();if(seconds<10)seconds='0'+seconds;var milli=o.getUTCMilliseconds();if(milli<100)milli='0'+milli;if(milli<10)milli='0'+milli;return'"'+year+'-'+month+'-'+day+'T'+ 12 | hours+':'+minutes+':'+seconds+'.'+milli+'Z"';} 13 | if(o.constructor===Array) 14 | {var ret=[];for(var i=0;i 3 | 4 | 5 | Chat 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 |
25 | Room name :
26 | 27 | 28 |
29 | 30 | 31 | 32 | 53 | 54 | 55 | 56 | 63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 | 71 | 75 | 76 | 77 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/com/h3xstream/rtmfp/Peer.as: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.h3xstream.rtmfp 4 | { 5 | import flash.net.NetStream; 6 | import flash.net.NetConnection; 7 | import flash.events.NetStatusEvent; 8 | import flash.events.AsyncErrorEvent; 9 | import flash.events.IOErrorEvent; 10 | import flash.events.StatusEvent; 11 | 12 | /** 13 | * Each peer is a connection from which we listen to receive new message. 14 | */ 15 | public class Peer 16 | { 17 | private var recvStream:NetStream = null; 18 | public var eventRecv:RtmfpConnection; 19 | public var peerID:String; //As documented by Adobe it's the "farID" 20 | 21 | public var connected:Boolean = true; 22 | 23 | /** 24 | * 25 | * @param peerID Id of the peer we will try to connect. 26 | * @param nc Connection handle to the rtmfp server 27 | * @param eventRecv Main class to which new events will be redirect. 28 | */ 29 | public function Peer (peerID:String, nc:NetConnection, eventRecv:RtmfpConnection) { 30 | this.recvStream = new NetStream(nc, peerID); 31 | this.recvStream.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler); 32 | this.recvStream.play("media"); 33 | 34 | this.recvStream.client = new Client(this); 35 | this.peerID = peerID; 36 | this.eventRecv = eventRecv; 37 | } 38 | 39 | private function netStatusHandler(event:NetStatusEvent):void{ 40 | Logger.log("(AS) PeerID:" + this.peerID + " Status:" + event.info.code); 41 | 42 | switch (event.info.code) { 43 | case "NetStream.Play.Start": //Peer is now ready to receive message 44 | this.eventRecv.onPeerConnect(this.peerID ); 45 | break; 46 | } 47 | } 48 | 49 | } 50 | 51 | 52 | 53 | } 54 | 55 | import com.h3xstream.rtmfp.*; 56 | 57 | /** 58 | * This class is use to sandbox the methods that can be call from remote. 59 | * The remote sender get to choose the method when calling the "NetStream.send()" method. 60 | * This could pose security risk if Peer contains method that could be abuse. 61 | */ 62 | internal class Client { 63 | public var peer:Peer = null; 64 | public function Client(peer:Peer) { 65 | this.peer = peer; 66 | } 67 | 68 | /** 69 | * Send a probe. 70 | */ 71 | public function receiveProbeRequest():void { 72 | Logger.log("(AS) Probe request from PeerID:" + peer.peerID); 73 | peer.eventRecv.onProbeRequest(); 74 | } 75 | 76 | /** 77 | * Confirm that the peer is alive. 78 | */ 79 | public function receiveProbeResponse():void { 80 | Logger.log("(AS) Probe response from PeerID:" + peer.peerID); 81 | peer.connected = true; 82 | } 83 | 84 | /** 85 | * The peer will receive only message that it has previously connected to 86 | * and that the remote peer has accepted. 87 | * @param message Content received 88 | */ 89 | public function receiveMessage(message:String):void { 90 | Logger.log("(AS) PeerID:"+peer.peerID+" message:"+message +""); 91 | peer.eventRecv.onMessageRecv(peer.peerID, message); 92 | } 93 | } -------------------------------------------------------------------------------- /rtmfp-api.as3proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 85 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | Rtmfp API 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 24 | 25 |
26 | 27 |

What is it?

28 |

This project expose Rtmfp protocol (provided by Flash version 10) to javascript application throught a hidden flash applet. 29 | The protocol allow multiple clients to communicate directly. See the references for more details about the protocol.

30 |

Possible uses include:

31 |
    32 |
  • Game
  • 33 |
  • Real-time communication
  • 34 |
  • High bandwith communication
  • 35 |
  • Chat application
  • 36 |
37 | 38 |

How to use it

39 |

40 | Initialisation:

41 |
 42 |     var config = {
 43 | 		DEBUG:false, //Internal debug of the Flash applet will be output to console.info
 44 | 		rtmfpUrl:'rtmfp://p2p.rtmfp.net/API_KEY'}; //Rtmfp server
 45 | 	
 46 | 	//Functions to handle the various events
 47 | 	var callbacks = {
 48 | 		onPeerIdRecvCall:     onPeerIdRecv, 
 49 | 		onMessageRecvCall:    onMessageRecv,
 50 | 		onPeerConnectCall:    onPeerConnect,
 51 | 		onPeerDisconnectCall: onPeerDisonnect};
 52 | 	
 53 | 	rtmfp = new Rtmfp("../../bin/rtmfp.swf",config,callbacks);
 54 |     
55 | 56 |

Define the different callbacks:

57 |
 58 |     onPeerIdRecv = function(peerId) {
 59 | 	    ...
 60 |     }
 61 |     onMessageRecv = function(peerId,cmd,object) {
 62 | 	    ...
 63 |     }
 64 |     onPeerConnect = function(peerId) {
 65 | 	    ...
 66 | 	    return true; //If the incoming connection is accept
 67 |     }
 68 |     onPeerDisconnect = function(peerId) {
 69 | 	    ...
 70 |     }
 71 |     
72 | 73 |

Use the main instance (Rtmfp object) to connect to peer and send message. 74 | The main principe of Rtmfp communication is that you can only send message 75 | to those who are connected to you.

76 |
 77 |      //Once connected messages can be _received_ from this peer
 78 |     rtmfp.connectToPeer("6487b[...]");
 79 |     
 80 |     //Publish message to all connected peers
 81 |     //The data passed can be any serializable js object
 82 |     rtmfp.send("NICKNAME","bob");
 83 |     rtmfp.send("LIST_ITEMS",["1","2","3","4"]);
 84 |     
85 | 86 |

Demos

87 | 88 | 91 | 92 |

References

93 | Rtmfp documentation
94 | 99 | If you want to modify the flash applet...
100 | 103 | Javascript libraries use
104 | 109 | 110 |
111 | 112 | 115 |
-------------------------------------------------------------------------------- /src/com/h3xstream/rtmfp/RtmfpConnection.as: -------------------------------------------------------------------------------- 1 | package com.h3xstream.rtmfp 2 | { 3 | import flash.net.NetConnection; 4 | import flash.net.NetStream; 5 | 6 | import flash.utils.Timer; 7 | import flash.utils.Dictionary; 8 | 9 | import flash.events.Event; 10 | import flash.events.NetStatusEvent; 11 | import flash.events.TimerEvent; 12 | 13 | /** 14 | * This class handle the initial communication with RTMFP server. 15 | * It listen for incoming connection. (Connections that come from other peer.) 16 | * 17 | * It will create a new Peer instance for every new connection. And keeps a list of 18 | * all the peers connected to (peers:Dictionary). 19 | * 20 | * Disconnect event those not specify which peerId is consern (at least from my knowledge). 21 | * When a "NetStream.Connect.Closed" is trigger, a probe is send to all the peers to confirm 22 | * their state. See the two probe method in the Peer class. 23 | */ 24 | public class RtmfpConnection 25 | { 26 | private var nc:NetConnection; 27 | private var sendStream:NetStream; 28 | 29 | private var peers:Dictionary = new Dictionary(); 30 | private var myID:String; 31 | 32 | private var timerWaitForProbeResp:Timer; 33 | private var timerStarted:Boolean = false; 34 | private var timerDelay:int = 2000; 35 | 36 | //Callbacks 37 | public var onMessageRecvCall:String = null; 38 | public var onPeerIdRecvCall:String = null; 39 | public var onPeerConnectCall:String = null; 40 | public var onPeerDisconnectCall:String = null; 41 | 42 | public function RtmfpConnection() 43 | { 44 | 45 | } 46 | 47 | /** 48 | * Establish connection to the RTMFP server. 49 | * The connection should result in the reception of "NetConnection.Connect.Success" 50 | * and a peer id. 51 | * @param serverAddr Rtmfp url (rtmfp://...) 52 | */ 53 | public function initServerConn(serverAddr:String):void { 54 | 55 | nc = new NetConnection(); 56 | nc.addEventListener(NetStatusEvent.NET_STATUS, netConnectionStatus); 57 | nc.connect(serverAddr); 58 | 59 | timerWaitForProbeResp = new Timer(timerDelay, 1); 60 | timerWaitForProbeResp.addEventListener(TimerEvent.TIMER, cleanUpPeerList); 61 | } 62 | 63 | /** 64 | * Listen for incomming connection. 65 | */ 66 | public function listen():void { 67 | this.sendStream = new NetStream(nc, NetStream.DIRECT_CONNECTIONS); 68 | this.sendStream.addEventListener(NetStatusEvent.NET_STATUS, netConnectionStatus); 69 | this.sendStream.publish("media"); 70 | 71 | var client:Object = new Object(); 72 | client.onPeerConnect = function(subscriber:NetStream):Boolean { 73 | Logger.log("(AS) Receive connection from " + subscriber.farID); 74 | var resp:Boolean = onPeerConnect(subscriber.farID); 75 | 76 | 77 | return resp; 78 | } 79 | 80 | this.sendStream.client = client; 81 | } 82 | 83 | /** 84 | * @param event Event related to the connection with rtmfp server (initServerConn method) 85 | * and related with the stream use to send messages to others (listen method) 86 | */ 87 | private function netConnectionStatus(event:NetStatusEvent):void { 88 | 89 | Logger.log("NC code="+event.info.code); 90 | 91 | switch (event.info.code) { 92 | case "NetConnection.Connect.Success": //Obtain ID from rtmfp server 93 | Logger.log("(AS) MyID:"+nc.nearID); 94 | this.myID = nc.nearID; 95 | 96 | onPeerIdRecv(this.myID); 97 | 98 | listen(); 99 | break; 100 | 101 | case "NetStream.Connect.Success": //Peer connect 102 | 103 | break; 104 | 105 | case "NetStream.Connect.Closed": //Peer disconnect 106 | if (timerStarted == false) { 107 | timerStarted = true; 108 | //Set every peer to disconnect 109 | for (var p:String in peers) { 110 | peers[p].connected = false; 111 | } 112 | //Send the probe 113 | sendStream.send("receiveProbeRequest"); 114 | //Wait 115 | timerWaitForProbeResp.start(); 116 | } 117 | 118 | break; 119 | 120 | case "NetStream.Publish.BadName": 121 | Logger.error("Please check the name of the publishing stream"); 122 | break; 123 | } 124 | } 125 | 126 | 127 | public function onProbeRequest():void { 128 | //log("Receive probe ...and replying") 129 | sendStream.send("receiveProbeResponse"); 130 | } 131 | 132 | /** 133 | * Get or create a peer connection. 134 | * The first call will established a connection with the remote Peer. 135 | * @param peerID 136 | */ 137 | public function getPeer(peerID:String):Peer { 138 | if (peers[peerID] == undefined) { 139 | peers[peerID] = new Peer(peerID, nc, this); 140 | } 141 | return peers[peerID]; 142 | } 143 | 144 | private function cleanUpPeerList(event:TimerEvent):void { 145 | for (var p:String in peers) { 146 | if (peers[p].connected == false) { 147 | //Probe not receive for this peer 148 | onPeerDisconnect(peers[p].peerID); 149 | } 150 | else { 151 | //Probe receive for this peer 152 | } 153 | } 154 | timerStarted = false; 155 | } 156 | 157 | /** 158 | * 159 | * @param peerID Establish a connection with the peer 160 | */ 161 | public function connectToPeer(peerID:String):void { 162 | Logger.log("Connecting to "+peerID); 163 | this.getPeer(peerID); 164 | } 165 | 166 | /** 167 | * Publish a message to whoever is listening. 168 | * @param message 169 | */ 170 | public function send(message:String):void { 171 | try { 172 | sendStream.send("receiveMessage", message); 173 | } 174 | catch (e:Error) { 175 | Logger.error('Could not send the message "'+message+'"',e); 176 | } 177 | } 178 | 179 | 180 | 181 | public function onPeerIdRecv(myID:String):void { 182 | Browser.jsCall(onPeerIdRecvCall, this.myID); 183 | } 184 | 185 | /** 186 | * Each message received is push to the browser. 187 | * @param peerID Source of the message 188 | * @param message Content 189 | */ 190 | public function onMessageRecv(peerID:String, message:String):void { 191 | Browser.jsCall(onMessageRecvCall, peerID, message); 192 | } 193 | 194 | public function onPeerConnect(peerID:String):* { 195 | var resp:* = Browser.jsCall(onPeerConnectCall, peerID); 196 | if (resp is Boolean) { 197 | Logger.log("Incoming connection from ["+peerID+"] "+(resp?"accepted":"refused")); 198 | return resp; 199 | } 200 | return true; 201 | } 202 | 203 | public function onPeerDisconnect(peerID:String):void { 204 | Browser.jsCall(onPeerDisconnectCall, peerID); 205 | } 206 | 207 | } 208 | 209 | } -------------------------------------------------------------------------------- /demos/chat/chat.js: -------------------------------------------------------------------------------- 1 | 2 | var url_params = loadQueryParams(); 3 | var rtmfp = null; 4 | 5 | /** 6 | * On document load 7 | */ 8 | $(document).ready(function() { 9 | initialiseRtmfp(); 10 | 11 | if(url_params['room']) { 12 | $("#overlay").show(); 13 | $("#divDialogConnect").show(); 14 | } 15 | }); 16 | 17 | function initialiseRtmfp() { 18 | var config = { 19 | DEBUG:false, 20 | rtmfpUrl:'rtmfp://p2p.rtmfp.net/f305c7dae01d5c4767797222-af55b81ce685'}; 21 | 22 | var callbacks = { 23 | onMessageRecvCall: ChatEvents.onRtmfpMessageRecv, 24 | onPeerIdRecvCall: ChatEvents.onRtmfpPeerIdRecv, 25 | onPeerConnectCall: ChatEvents.onRtmfpPeerConnect, 26 | onPeerDisconnectCall: ChatEvents.onRtmfpPeerDisconnect}; 27 | 28 | rtmfp = new Rtmfp("../rtmfp.swf",config,callbacks); 29 | } 30 | 31 | /** 32 | * 33 | */ 34 | User = function (nickname,peerId) { 35 | this.nickName = nickname; 36 | this.peerId = peerId; 37 | this.color = randomColor(); 38 | } 39 | 40 | /** 41 | * Static variables for room state. 42 | */ 43 | ChatState = function () { 44 | var pub = {}; 45 | 46 | pub.addUser = function(user) { 47 | if(pub.getUserByPeerId(user.peerId) == null) { 48 | ChatState.listUsers.push(user); 49 | } 50 | } 51 | 52 | pub.removeUser = function(peerId) { 53 | var users = ChatState.listUsers; 54 | for(var u in users) { 55 | if(users[u].peerId == peerId) { 56 | users.splice (u,1); 57 | return; 58 | } 59 | } 60 | } 61 | 62 | pub.getUserByPeerId = function(peerId) { 63 | var users = ChatState.listUsers; 64 | for(var u in users) { 65 | if(users[u].peerId == peerId) 66 | return users[u]; 67 | } 68 | return null; 69 | } 70 | 71 | return pub; 72 | }(); 73 | 74 | ChatState.isChatOwner = false; 75 | ChatState.connected = false; 76 | 77 | ChatState.roomTitle = ""; 78 | ChatState.roomId = ""; 79 | ChatState.listUsers = []; 80 | ChatState.currentUser = new User("Anonymous"); 81 | ChatState.addUser(ChatState.currentUser); 82 | 83 | 84 | /** 85 | * Actions 86 | */ 87 | ChatActions = function () { 88 | var pub = {}; 89 | 90 | pub.switchToRoom = function(roomTitle) { 91 | ChatState.roomTitle = roomTitle; 92 | $("#room_title").text(roomTitle); 93 | 94 | if(!ChatState.connected) { 95 | $("#divDialogConnect").hide(); 96 | $("#section_create").hide(); 97 | $("#section_chat").fadeIn(1500); 98 | 99 | $("#overlay").show(); 100 | $("#divDialogNickName").show(); 101 | 102 | ChatState.connected = true; 103 | 104 | ChatUI.showInviteUrl(); 105 | } 106 | } 107 | 108 | pub.createRoom = function(roomName) { 109 | if(roomName == '') 110 | roomName = 'Untitled'; 111 | 112 | ChatState.isChatOwner = true; 113 | ChatState.roomId = ChatState.currentUser.peerId; 114 | pub.switchToRoom(roomName); 115 | } 116 | 117 | pub.loadRoom = function(roomId) { 118 | ChatState.roomId = roomId; 119 | rtmfp.connectToPeer(roomId); 120 | } 121 | 122 | /** When a regular peer send a message **/ 123 | pub.sendMessage = function(message) { 124 | 125 | if(!ChatState.isChatOwner) { 126 | rtmfp.send("MSG",message); 127 | } 128 | else { 129 | var myId = ChatState.currentUser.peerId; 130 | pub.broadcastMessage(myId,message); 131 | ChatUI.printMessage(myId,message); 132 | } 133 | } 134 | 135 | /** Chat owner broadcast to all peers **/ 136 | pub.broadcastMessage = function(peerId,message) { 137 | var msg = {}; 138 | msg['peerId'] = peerId; 139 | msg['message'] = message; 140 | 141 | rtmfp.send("MSGB",msg); 142 | } 143 | 144 | pub.changeNickName = function(nickName) { 145 | if(nickName == '') 146 | nickName = 'Anonymous'; 147 | 148 | if(!ChatState.isChatOwner) { 149 | rtmfp.send("NICK",nickName); 150 | } 151 | 152 | ChatState.currentUser.nickName = nickName; 153 | ChatUI.updateListUsers(); 154 | 155 | $("#overlay").hide(); 156 | $("#divDialogNickName").hide(); 157 | } 158 | 159 | pub.sendListUsers = function() { 160 | var listUsers = $.extend(true,[],ChatState.listUsers); 161 | 162 | rtmfp.send("USERS",listUsers); 163 | } 164 | 165 | pub.sendRoomTitle = function() { 166 | rtmfp.send("ROOM",ChatState.roomTitle); 167 | } 168 | 169 | return pub; 170 | }(); 171 | 172 | /** 173 | * Events 174 | */ 175 | ChatEvents = function () { 176 | var pub = {}; 177 | 178 | pub.onRtmfpMessageRecv = function(peerId,cmd,data) { 179 | 180 | 181 | //console.info("(JS) New message [cmd]="+cmd+" [data]="+data); 182 | 183 | var user = ChatState.getUserByPeerId(peerId); 184 | 185 | if(cmd == 'USERS') { 186 | ChatState.listUsers = data; 187 | ChatUI.updateListUsers(); 188 | } 189 | else if(cmd == 'ROOM') { 190 | ChatActions.switchToRoom(data); 191 | } 192 | else if(cmd == 'MSG') { 193 | ChatUI.printMessage(user.peerId,data); 194 | if(ChatState.isChatOwner) { 195 | ChatActions.broadcastMessage(user.peerId,data); 196 | } 197 | } 198 | else if(cmd == 'MSGB') { 199 | if(!ChatState.isChatOwner) { 200 | var userMsg = ChatState.getUserByPeerId(data['peerId']); 201 | ChatUI.printMessage(userMsg.peerId,data['message']); 202 | } 203 | } 204 | else if(cmd == 'NICK') { 205 | if(data == '') 206 | data = 'Anonymous'; 207 | 208 | user.nickName = data; 209 | ChatUI.updateListUsers(); 210 | ChatActions.sendListUsers(); 211 | } 212 | } 213 | 214 | pub.onRtmfpPeerIdRecv = function(peerId) { 215 | ChatState.currentUser.peerId = peerId; 216 | 217 | if(url_params['room']) { 218 | ChatActions.loadRoom(url_params['room']); 219 | } 220 | } 221 | 222 | pub.onRtmfpPeerConnect = function(peerId) { 223 | if(ChatState.isChatOwner) { 224 | //Established the other way 225 | rtmfp.connectToPeer(peerId); 226 | 227 | var u = new User("Guest",peerId); 228 | ChatState.addUser(u); 229 | ChatActions.sendListUsers(); 230 | ChatActions.sendRoomTitle(); 231 | } 232 | else { 233 | } 234 | 235 | return true; 236 | } 237 | 238 | pub.onRtmfpPeerDisconnect = function(peerId) { 239 | if(ChatState.isChatOwner) { 240 | ChatState.removeUser(peerId); 241 | 242 | ChatUI.updateListUsers(); 243 | ChatActions.sendListUsers(); 244 | } 245 | else { 246 | //Lost communication with the room owner 247 | } 248 | } 249 | 250 | return pub; 251 | }(); 252 | 253 | /** 254 | * Change various UI elements. 255 | */ 256 | ChatUI = function () { 257 | var pub = {}; 258 | 259 | pub.printMessage = function (peerId,message) { 260 | var u = ChatState.getUserByPeerId(peerId); 261 | $("#divMessages").append(""+escapeHtml(u.nickName)+": "+escapeHtml(message)+"
"); 262 | 263 | $("#divMessages")[0].scrollTop = $("#divMessages")[0].scrollHeight; //Scroll to bottom 264 | } 265 | 266 | pub.printNotice = function(notice) { 267 | $("#divMessages").append(""+escapeHtml(notice)+""); 268 | } 269 | 270 | pub.updateListUsers = function() { 271 | var ul = $('ul#ulListPeers').empty(); 272 | 273 | for(var userIdx in ChatState.listUsers) { 274 | var user = ChatState.listUsers[userIdx]; 275 | ul.append("
  • "+escapeHtml(user.nickName)+"
  • "); 276 | } 277 | } 278 | 279 | pub.showInviteUrl = function() { 280 | var href=window.location.href+"?"; 281 | hrefCrop = href.slice(0,href.indexOf('?')); 282 | $("#txtInviteUrl").val(hrefCrop+"?room="+ChatState.roomId); 283 | $("#divInvite").show(); 284 | } 285 | 286 | return pub; 287 | }(); 288 | 289 | /** 290 | * Escape special characters 291 | */ 292 | function escapeHtml(someText) { 293 | return $('
    ').text(someText).html(); 294 | } 295 | 296 | function randomColor() { 297 | var r = Math.floor((Math.random()*160)+40).toString(16); 298 | var g = Math.floor((Math.random()*160)+40).toString(16); 299 | var b = Math.floor((Math.random()*160)+40).toString(16); 300 | 301 | return '#'+r+g+b; 302 | } 303 | 304 | function loadQueryParams() { 305 | var query = window.location.search; 306 | var params = []; 307 | 308 | var listTmp = query.slice(query.indexOf('?') + 1).split('&'); 309 | for(var h in listTmp) { 310 | var split = listTmp[h].split('='); 311 | params[split[0]] = split[1]; 312 | } 313 | return params; 314 | } -------------------------------------------------------------------------------- /demos/common-js/swfobject.js: -------------------------------------------------------------------------------- 1 | /* SWFObject v2.2 2 | is released under the MIT License 3 | */ 4 | var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab