AS3 - Local P2P networking receiving only "undefined" data - actionscript-3

I'm trying to make a simple P2P networking system to send messages to other Flash Players on your network.
However, when I send a message the receiving player doesn't receive a message but rather a undefined object.
I did allow Flash Player in my Virus Scanner (NOD 32) and my Windows Firewall by the way.
Here's my P2P class:
package com.snakybo.as3framework.network {
import flash.events.Event;
import flash.events.NetStatusEvent;
import flash.net.GroupSpecifier;
import flash.net.NetConnection;
import flash.net.NetGroup;
/** #author Kevin */
public class P2P extends NetConnection {
private var netCon:NetConnection;
private var netGroup:NetGroup;
private var handler:Function;
private var groupName:String;
public function P2P(groupName:String, handler:Function) {
this.groupName = groupName;
this.handler = handler;
netCon = new NetConnection();
netCon.addEventListener(NetStatusEvent.NET_STATUS, conHandler);
netCon.connect("rtmfp:");
}
/** Post a message */
public function post(txt:*):void {
var data:Object = new Object();
data["txt"] = txt;
data["id"] = new Date().time;
netGroup.post(data);
}
/** Handle connection event listener */
private function conHandler(e:NetStatusEvent):void {
if(e.info["code"] == "NetConnection.Connect.Success") {
setupGroup();
}
}
/** Connect to group */
private function setupGroup():void {
var groupSpec:GroupSpecifier = new GroupSpecifier(groupName);
groupSpec.postingEnabled = true;
groupSpec.ipMulticastMemberUpdatesEnabled = true;
groupSpec.addIPMulticastAddress("225.225.0.1:30000");
netGroup = new NetGroup(netCon, groupSpec.groupspecWithAuthorizations());
netGroup.addEventListener(NetStatusEvent.NET_STATUS, handler);
dispatchEvent(new Event(Event.COMPLETE));
}
}
}
Connection seems to be working, since I can send messages and the other player does receive them.. well.. receive something.
Then from my Main.as I call this class like this:
private var p2p:P2P;
public function Main():void {
p2p = new P2P("snakybo", netHandler);
p2p.addEventListener(Event.COMPLETE, onConnected);
}
private function onConnected(e:Event):void {
function send(e:KeyboardEvent):void {
c.post("a");
trace("poseted");
}
stage.addEventListener(KeyboardEvent.KEY_DOWN, send);
}
private function netHandler(e:NetStatusEvent):void {
switch(e.info["code"]) {
case "NetGroup.Posting.Notify":
trace("received: " + e.info["txt"]);
break;
}
}
Also, if I add:
case "NetGroup.Connection.Success":
trace("Connected to group");
break;
It never prints that. Not sure what's wrong with that.
Am I doing anything wrong here? Or did my PC somehow block Flash Player?

In your P2P class you have this...
netCon.connect("rtmfp:");
This tells your NetConnection to connect to a string ("rtmfp:") which isn't a server or valid connection type if you are trying to reach outside members of the rtmfp connection. It appears you are using this for an internal network so "rtmfp" will work locally.
What I do with NetConnection.connect() is I set up my rtmfp constant and a developer key constant (for cirrus server) right off the bat like this...
cons SERVER:String = "rtmfp:";
cons DEVKEY:String = "your developer key here";
then
function connect(): void {
nc = new NetConnection();
nc.addEventListener(NetStatusEvent.NET_STATUS, netStatus);
nc.connect(SERVER + DEVKEY);
}
You can go to adobes website if you are using their rtmfp server to get a developer key, they are free and easy to get anyway. Once you are connected you will get a NetStatus event string of "NetConnection.Connect.Success" which your conHandler function is set up to receive. I use a Switch Case within mine to listen for the connect string and do something based off each string like this:
function netStatus(event: NetStatusEvent): void {
switch (event.info.code) {
case "NetConnection.Connect.Success":
setupGroup();
break;
} }
Fire your setupGroup() function when you hear "NetConnection.Connect.Success". Then within your setupGroup() go ahead and set your
groupspec.serverChannelEnabled = true;
groupspec.routingEnabled = true;
groupspec.objectReplicationEnabled = true; // and I have
groupspec.peerToPeerDisabled = false;
Also, in your setupGroup you are firing "handler" instead of "conHandler" which is the name of your NetStatus event function handler. Probably just a typo. Setting the new NetGroup itself will fire 1 of 3 NetStatus events telling you the status of the new netgroup connection so you won't need to dispatch the event you are using unless you want to, up to you. You can handle those in your conHandler function as well.
I will typically have a trace statement setup to print out the NetStatusEvent so in your case that would be... trace(e.info.code) and you will be able to see the events as they happen and be able to troubleshoot problems much easier. Once you get this connection up you'll be able to handle the delivering of objects.
This should at least get you a created instance and the connection to others in the group. I have included a couple links I referenced here:
NetGroup
NetStatus
NetConnection
Try googling Tom Krcha, he is with adobe and has some good info on this as well.

Related

Worker not receiving messages after RUNNING state, but it does later

Update: Apparently, it's some kind of timing issue. In the code below I create the worker from a click handler, but if I create the worker from the Main constructor (as the doc example does), the commandChannel message gets to the worker -- even though in either case I'm doing the exact same thing: create the worker, listen for RUNNING state, then send message over commandChannel. But that first message only gets through if I create the worker from the Main constructor, while messages later in time (some undetermined timed after RUNNING) always get through. Why? If I can't rely on WorkerState.RUNNING to know when I can send messages to the worker, how can I know when the worker is ready to receive messages?
I basically have the exact same thing as the documentation example, but it's not working. My setup is:
Flash Pro CC
AIR 17 for Desktop
Windows 7
The problem is that the worker does not seem to receive messages through the incoming message channel, though it can successfully send messages to the main SWF through an outgoing message channel.
This is my main class:
public class Main extends MovieClip {
[Embed(source="TestWorker.swf", mimeType="application/octet-stream")]
private var WorkerSWF:Class;
private var workerBytes:ByteArray;
private var worker:Worker;
private var commandChannel:MessageChannel;
private var resultChannel:MessageChannel;
public function Main() {
workerBytes = new WorkerSWF();
stage.addEventListener(MouseEvent.CLICK, clickHandler);
}
private function clickHandler(e:MouseEvent):void {
stage.removeEventListener(MouseEvent.CLICK, clickHandler);
startWorker();
}
private function startWorker():void {
trace("Start worker");
worker = WorkerDomain.current.createWorker(workerBytes, true);
commandChannel = Worker.current.createMessageChannel(worker);
worker.setSharedProperty("commands", commandChannel);
resultChannel = worker.createMessageChannel(Worker.current);
resultChannel.addEventListener(Event.CHANNEL_MESSAGE, resultMessageHandler);
worker.setSharedProperty("results", resultChannel);
worker.addEventListener(Event.WORKER_STATE, workerStateHandler);
worker.start();
}
private function workerStateHandler(e:Event):void {
trace("Worker state:", worker.state);
if(worker.state == WorkerState.RUNNING) {
trace("Worker running");
commandChannel.send("START PLEASE");
}
}
private function resultMessageHandler(e:Event):void {
trace(e, resultChannel.receive())
}
}
And my worker SWF class:
public class TestWorker extends Sprite {
private var commandChannel:MessageChannel;
private var resultChannel:MessageChannel;
public function TestWorker () {
trace("isPrimordial:", Worker.current.isPrimordial)
commandChannel = Worker.current.getSharedProperty("commands") as MessageChannel;
commandChannel.addEventListener(Event.CHANNEL_MESSAGE, commandMessageHandler);
resultChannel = Worker.current.getSharedProperty("results") as MessageChannel;
resultChannel.send("Hello from worker");
}
private function commandMessageHandler(e:Event):void {
trace("commandMessageHandler")
//var message:String = commandChannel.receive();
resultChannel.send("Whatever.");
setInterval(respond, 1000);
}
private function respond(){
resultChannel.send("Whatever " + new Date());
}
}
The problem is that the worker's commandMessageHandler is never triggered. I know this because I never receive the "Whatever" message back to the Main SWF. The "Hello from worker" message does get back to the Main SWF, so it's something specific to the main-to-worker message channel. What's wrong?
Additionally, it seems that neither trace actions or runtime errors from the worker are captured by the main SWF. How does one debug a worker SWF?
This seems to be a known issue (see point 2). WorkerState.RUNNING sometimes gets dispatched before the constructor of the worker has actually run. So in my case, from the click handler, the first message got sent before the constructor of the worker had added the listener to receive it.
So I'm going with the simple workaround: when the worker loads, send a message to the main thread that says "I'm ready" at the end of the constructor. In the main thread, listen for that instead of RUNNING before sending the intial command.

Geolocation StatusEvent Never Fires

I am developing an AIR for iOS app and I have just this one issue left on my list of bugs to fix. Basically, when a certain window is opened, the user is able to use their current position to search. I am using the Geolocation class for this and it works fine, except for the fact that StatusEvent.STATUS is not firing. The user is prompted to give permission to use their current location, as it should, but when they choose, my StatusEvent handler is never called.
if ( Geolocation.isSupported ) {
var geo:Geolocation = new Geolocation();
geo.addEventListener( StatusEvent.STATUS, this.geolocationStatusHandler );
this.geolocationStatusHandler();
}
protected function geolocationStatusHandler( e:StatusEvent = null ):void{
var geo:Geolocation = new Geolocation();
this.gpsButton.enabled = !geo.muted;
}
The only thing I can think of is that the alert window (which freezes app execution) is opened with new Geolocation(), before I add the event listener. But if that were the case, calling the handler manually wouldn't occur until after the user closed the Alert (it happens at the same time, roughly. A breakpoint there stops the app while the alert is open)
Is there a solution for this? I could always move the prompt to the very beginning of the app, but I personally prefer not being prompted until it is needed.
Details:
ActionScript Mobile project built with AIR 3.6 SDK
Tested on iPad 2, 3, and Mini, all running iOS 6.1.3
Tested in both release and debug modes
Listen for the GeolocationEvent.UPDATE event.
Also, it appears you're manually calling the handler immediately after the listener; then your handler is instantiating a new Geolocation instead of getting GeolocationEvent latitude and longitude.
Example implementation using Google Geocoding API from XpenseIt tutorial:
package
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.GeolocationEvent;
import flash.sensors.Geolocation;
import mx.rpc.AsyncResponder;
import mx.rpc.AsyncToken;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;
[Event(name="locationUpdate", type="flash.events.Event")]
public class GeolocationUtil extends EventDispatcher
{
protected var geo:Geolocation;
public var updateCount:int;
protected var service:HTTPService = new HTTPService();
public var location:String;
public var longitude:Number;
public var latitude:Number;
public function GeolocationUtil()
{
service.url = "https://maps.googleapis.com/maps/api/geocode/xml";
}
public function geoCodeAddress(address: String):AsyncToken
{
return service.send({address: address, sensor: Geolocation.isSupported});
}
public function getLocation():void
{
if (Geolocation.isSupported)
{
geo = new Geolocation();
geo.setRequestedUpdateInterval(500);
updateCount = 0;
geo.addEventListener(GeolocationEvent.UPDATE, locationUpdateHandler);
}
}
protected function locationUpdateHandler(event:GeolocationEvent):void
{
// Throw away the first location event because it's almost always the last known location, not current location
updateCount++;
if (updateCount == 1) return;
if (event.horizontalAccuracy <= 150)
{
trace("lat:" + event.latitude + " long:" + event.longitude + " horizontalAccuracy:" + event.horizontalAccuracy);
geo.removeEventListener(GeolocationEvent.UPDATE, locationUpdateHandler);
geo = null;
}
longitude = event.longitude;
latitude = event.latitude;
var token:AsyncToken = service.send({latlng: latitude+","+longitude, sensor: Geolocation.isSupported});
token.addResponder(new AsyncResponder(
function(event:ResultEvent, token:AsyncToken):void
{
// Map the location to city and state from the response address component
location = event.result.GeocodeResponse.result[0].address_component[3].long_name + ', '+ event.result.GeocodeResponse.result[0].address_component[5].long_name;
dispatchEvent(new Event("locationUpdate"));
},
function (event:FaultEvent, token:AsyncToken):void
{
// fail silently
trace("Reverse geocoding error: " + event.fault.faultString);
}));
}
}
}

Actionscript 3 - checking for an internet connection

I am using this code in my flash file
import air.net.URLMonitor;
import flash.net.URLRequest;
import flash.events.StatusEvent;
var monitor:URLMonitor;
function checkInternetConnection(e:Event = null):void
{
var url:URLRequest = new URLRequest("http://www.google.com");
url.method = "HEAD";
monitor = new URLMonitor(url);
monitor.pollInterval = 1000;
//
monitor.addEventListener(StatusEvent.STATUS, checkHTTP);
//
function checkHTTP(e:Event = null):void
{
if (monitor.available) {
navigateToURL(new URLRequest("flickr.html"),"_top");
} else {
gotoAndPlay(1);
}
}
monitor.start();
}
I am trying to get the flash to check for a connection and navigate to another page if not it will replay.
It doesnt seem to be working.
Am i missing anything?
I have add the library path to aircore.swc also.
Its meant to be a html page with flash rather than an AIR app
Cheers
(My reputation is too low to comment on Tianzhen Lin's answer directly ...)
I needed to make a few changes in order to get Tianzhen Lin's answer to work as expected:
Added:
urlRequest.useCache = false;
urlRequest.cacheResponse = false;
This addition was required because even when the connection was definitely lost, the check was still succeeding because the cache was being read from.
Changed:
if( textReceived.indexOf( _contentToCheck ) )
to:
if( !(textReceived.indexOf( _contentToCheck ) == -1) )
This change was required because while "" (an empty string) was always being found, it was being found at index '0' which was causing the original if() condition to always fail.
Added:
urlRequest.idleTimeout = 10*1000;
In the case where the network cable was physically disconnected from the router, the request could take a very long time to time-out (Honestly, I got tired of waiting after a couple of minutes.)
After making the above changes, I found Tianzhen's code worked perfectly: No matter how I went about disconnecting/reconnecting Wi-Fi (on both iOS and Android devices) the change in connection status was always detected.
I see two issues in your code. One is that while you have the logic to check internet connection, there is not any code calling the function, therefore the logic of redirection would not be called. Second is that using AIRcore.swc would be a bad idea as you have injected a dependency that might not work with or violate browser sandbox.
You may try the following approach which is tested and requires no AIR's SWC:
Step 1, include a new class ConnectionChecker as follow:
package
{
import flash.events.*;
import flash.net.*;
[Event(name="error", type="flash.events.Event")]
[Event(name="success", type="flash.events.Event")]
public class ConnectionChecker extends EventDispatcher
{
public static const EVENT_SUCCESS:String = "success";
public static const EVENT_ERROR:String = "error";
// Though google.com might be an idea, it is generally a better practice
// to use a url with known content, such as http://foo.com/bar/mytext.txt
// By doing so, known content can also be verified.
// This would make the checking more reliable as the wireless hotspot sign-in
// page would negatively intefere the result.
private var _urlToCheck:String = "http://www.google.com";
// empty string so it would always be postive
private var _contentToCheck:String = "";
public function ConnectionChecker()
{
super();
}
public function check():void
{
var urlRequest:URLRequest = new URLRequest(_urlToCheck);
var loader:URLLoader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.TEXT;
loader.addEventListener(Event.COMPLETE, loader_complete);
loader.addEventListener(IOErrorEvent.IO_ERROR, loader_error);
try
{
loader.load(urlRequest);
}
catch ( e:Error )
{
dispatchErrorEvent();
}
}
private function loader_complete(event:Event):void
{
var loader:URLLoader = URLLoader( event.target );
var textReceived:String = loader.data as String;
if ( textReceived )
{
if ( textReceived.indexOf( _contentToCheck ) )
{
dispatchSuccessEvent();
}
else
{
dispatchErrorEvent();
}
}
else
{
dispatchErrorEvent();
}
}
private function loader_error(event:IOErrorEvent):void
{
dispatchErrorEvent();
}
private function dispatchSuccessEvent():void
{
dispatchEvent( new Event( EVENT_SUCCESS ) );
}
private function dispatchErrorEvent():void
{
dispatchEvent( new Event( EVENT_ERROR ) );
}
}
}
Step 2, in your main application or anywhere that internet connection should be checked, use the following snippet:
var checker:ConnectionChecker = new ConnectionChecker();
checker.addEventListener(ConnectionChecker.EVENT_SUCCESS, checker_success);
checker.addEventListener(ConnectionChecker.EVENT_ERROR, checker_error);
checker.check();
private function checker_success(event:Event):void
{
// There is internet connection
}
private function checker_error(event:Event):void
{
// There is no internet connection
}
The air.net.URLMonitor is the AIR available class - so will not work outside of the AIR player.
But you could try to do your own as all this monitor class does is "ping" the url and check the response, so you could do similar try to load something from known source like google.com and if it completes wihtout error then it is OK otherwise you will get error.

URLLoader doesn't even try to load. Error #2032: Stream Error

I am trying to use URLLoader to load an XML file from server (same domain as swf). This should be too simple but I am getting Error #2032: Stream Error
If I trace the HTTP status event it just shows status=0 though I have read that Mozilla doesn't supply status codes to Flash Player so maybe it's not informative.
I have sniffed the HTTP traffic with Charles and Flash isn't even trying to load the url - no request is made, so it doesn't even fail.
I can browse to the url, which is on an internal url that looks like: http://media.project:8080/audio/playlist.xml
I have tried putting a crossdomain.xml in there (with and without a to-ports="8080"), though it shouldn't need one.
Neither the onOpen nor onActivate events fire, just one HTTPStatus and then the IOError.
I have copied the common URLLoader code from Adobe example, mine looks like this:
public class PlaylistLoader extends EventDispatcher
{
public var xmlLoader:URLLoader;
public var url:String = '';
public function PlaylistLoader(url:String)
{
url = url;
trace(url);
xmlLoader = new URLLoader();
xmlLoader.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
xmlLoader.addEventListener(Event.COMPLETE, onResult);
xmlLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
xmlLoader.addEventListener(Event.OPEN, onOpen);
xmlLoader.addEventListener(Event.ACTIVATE, onActivate);
}
public function loadData():void {
var req:URLRequest = new URLRequest(url);
trace(req);
xmlLoader.load(req);
}
protected function onResult(e:Event):void
{
var xmlData:XML = e.target.data as XML;
parseData(xmlData);
}
private function httpStatusHandler(event:HTTPStatusEvent):void {
trace("httpStatusHandler: " + event);
}
protected function onOpen(e:Event):void
{
trace(e);
}
protected function onActivate(e:Event):void
{
trace(e);
}
protected function onIOError(e:IOErrorEvent):void
{
trace(e);
}
url = url;
in the constructor sets the local url to the local url. If you want to save that value inside of the object, you need to reference the object member url explicitely (or use a different name):
this.url = url;
it seems url string still empty
public var url:String = '';
isn't it?
try
public var url:String = 'http://media.project:8080/audio/playlist.xml';

AS3 driving me nuts

Ok here is what I am currently trying to do. I have a class called vdata.as which takes 2 paramaters both are strings sent from the main stage. Parameter one is the location for an XML file that I need to open and read. The second parameter is the name of the video I am currently looking for.
Now I can get the data from the XML file and display it with out any issue if its called from my class but when I try to access any of it from the stage I get undefined.
import flash.net.*;
import flash.display.*;
import flash.events.*;
public class videoData
{
private var mName:String;
private var mLink:String;
private var mCategory:String;
public static var elementArray:Array;
// Constructor
public function videoData(xmlPath:String,xmlVidSrc:String,pMC:MovieClip)
{
pXmlPath = xmlPath;
pXmlVidSrc = xmlVidSrc;
xmlloader = new URLLoader();
elementArray = new Array();
}
public function getXML()
{
XMLData();
}
private function XMLData()
{
xmlloader.load(new URLRequest(pXmlPath));
xmlloader.addEventListener(Event.COMPLETE,parseXMLData);
}
private function parseXMLData():void
{
var x:XML = new XML(xmlloader.data);
Init(x);
}
private function Init(m:XML):*
{
var i:Number;
for(i=0; i<m.videos.videoname.length(); i++)
{
if(m.videos.videoname[i].#name == pXmlVidSrc)
{
videoData.elementArray.push(m.videos.videoname[i].#name);
videoData.elementArray.push(m.videos.videoname[i].#category);
videoData.elementArray.push(m.videos.videoname[i].link.#url);
}
}
}
}
When I call it from the main stage the code is as follows.
var xData:videoData = new videoData(xmlPath,vidSrc,this);
xData.getXML();
then when I try to access any elements of videoData.elementArray they come up undefined...
Im just smacking my head on my desk trying to figure this out any help would be great.
Why is elementArray a static var, you only need to make it public to use it outside the function.
I'm quite confusing but you may want to try a debugging tool like "De MonsterDebugger", I would start by tracing xmlloader.data in the parseXMLData function.
"addEventListener" doesn't "fire"...the event does. You'll need to add a boolean to state for the stage that elementArray has been populated and set that after the init function.
Is elementArray something that needs to be true across all instances of videoData? If not, it shouldn't be static. You can use MovieClip(this.root).xData to access that instance of the video class from one of your other classes.
If the event has completed and the array is still empty - then it wasn't populated by your parser. You can also do checks to see if the elementArray.length > 0.
EDIT in response to comment:
as a public member or preferably a read-only property make a boolean variable:
var parseComplete:Boolean;
Set it to false in your constructor.
Then, after your call to "Init" in your Event.COMPLETE callback set:
parseComplete=true;
Then make sure parseComplete == true before you ever access elementArray. If you're waiting for the parser to complete you might want to set a timeout or some sort of try/catch mechanism just in case there are any unforeseen errors that would cause some sort of:
while( !xData.parseComplete ) { }
To loop indefinitely. It all depends on the usage. Personally I'd probably add a callback from the event listener to the stage to trigger whatever is supposed to happen next.