Erain 3D
--> FDT Pure coding comfort


RedSandy Project

Introducing RedSandy-0.9 - Release version to coincide with concurrent red5 version.

RedSandy project and SVN : http://code.google.com/p/redsandy/

My Blog : http://thebitstream.com

The Latest

Cue Points

http://www.thebitstream.com/CuePointsPlayer.swf

We're coming close to another Red5 release, and so I thought it a good time to stir the pot. I used the red5 sandy application to record a flv stream.

This stream did not contain any microphone or camera data, but instead contained script data tags. Using..

ns.send("@setDataFrame","onAnimation",{frame:j,sin:Math.sin(j * 2 * Math.PI /180)});

…in one swf, I call that script at 40 or so millisecond intervals while recording a flv file on red5 with the sandy application.

In another swf, I play the flv file and use the script data to link to my primitives.

Each call from the broadcaster triggers the sandy world to render. Fun stuff!

Note:The playback code is not using rtmp.

package
{
	import flash.display.Sprite;
	import flash.events.NetStatusEvent;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.net.ObjectEncoding;
 
	import sandy.core.Scene3D;
	import sandy.core.scenegraph.Camera3D;
	import sandy.core.scenegraph.Group;
	import sandy.core.scenegraph.TransformGroup;
	import sandy.materials.Appearance;
	import sandy.materials.ColorMaterial;
	import sandy.primitive.Plane3D;
	import sandy.primitive.Sphere;
 
 
	public class CuePointsPlayer extends Sprite
	{
		private var world:Scene3D;
		private var camera:Camera3D;
		private var group:Group;		
		var nc:NetConnection=new NetConnection();
		var ns:NetStream;		
		public function CuePointsPlayer()
		{
 
			super();
			camera = new Camera3D(400,400);
        	group=new Group();
        	world =new Scene3D('avatar'+Math.random(),this,camera, group);
        	world.camera.near=.01; 
			world.rectClipping=false;
			camera.z=-300;
			var b:Sphere=new Sphere('sphereShape',25);
 
 
 
			var tg:TransformGroup=new TransformGroup("sphere");
			tg.addChild(b);
 
 
			var plane:Plane3D=new Plane3D("d");
			plane.z=200;
			world.root.addChild(plane);
			world.root.addChild(tg);
 
			NetConnection.defaultObjectEncoding=ObjectEncoding.AMF0;
			nc.addEventListener(NetStatusEvent.NET_STATUS,onStatus);
			nc.objectEncoding=ObjectEncoding.AMF0;
 
			nc.client=this;	
			nc.connect(null);//local 
			ns=new NetStream(nc);
			ns.addEventListener(NetStatusEvent.NET_STATUS,onStatus);
			ns.client=this;
			ns.bufferTime=4;
			ns.play("http://thebitstream.com/CuePoints.flv");				
 
 
		}
		public function onStatus(e :NetStatusEvent):void
		{
			trace(e.info.code);
			switch(e.info.code)
			{
				case "NetConnection.Connect.Success":
 
				break;
 
 
				break;
			}		
		}
		public function onMetaData(obj:Object):void
		{
 
		}		
		public function onSandy(obj:Object):void
		{
 
		}
		public function onPlayStatus(obj:Object):void
		{
 
		}
		public function onAnimation(obj:Object,onj:Object=null):void
		{
			TransformGroup(world.root.getChildByName("sphere")).rotateY=obj.frame*5;
			TransformGroup(world.root.getChildByName("sphere")).y=100*obj.sin;
			TransformGroup(world.root.getChildByName("sphere")).x=50*obj.sin;
			camera.x=obj.sin*60*-1;
			camera.y=obj.sin*60*-1;
			camera.lookAt(TransformGroup(world.root.getChildByName("sphere")).x,TransformGroup(world.root.getChildByName("sphere")).y,TransformGroup(world.root.getChildByName("sphere")).z)
 
			world.render();
 
			trace("onAnimation");
		}		
	}
}

The flv can be found here, http://www.thebitstream.com/CuePoints.flv

package
{
	import flash.display.Sprite;
	import flash.events.AsyncErrorEvent;
	import flash.events.NetStatusEvent;
	import flash.media.Microphone;
	import flash.media.Camera;	
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.net.ObjectEncoding;
	import flash.utils.setInterval;	
import flash.utils.setTimeout;	
	public class CuePointsRecorder extends Sprite
	{
		var nc:NetConnection=new NetConnection();
		var ns:NetStream;
		var j:int=0;		
		public function CuePointsRecorder()
		{
			NetConnection.defaultObjectEncoding=ObjectEncoding.AMF0;
			nc.addEventListener(NetStatusEvent.NET_STATUS,onStatus);
			nc.addEventListener(NetStatusEvent.NET_STATUS,onStatus);		
			nc.client=this;	
			nc.objectEncoding=ObjectEncoding.AMF0;
			nc.addEventListener(AsyncErrorEvent.ASYNC_ERROR, onError);
			nc.connect("rtmp://localhost/sandy");				
		}
		public function onError(e :AsyncErrorEvent):void
		{
			trace(e.text);
		}
		public function onSandy(e :Object):void
		{
 
		}
		public function onStop():void
		{
			ns.close();
		}		
		public function onMetaData(e:Object):void
		{
 
		}
		public	function onAnimationTrigger():void
		{
			j++;
 
			ns.send("@setDataFrame","onAnimation",{frame:j,sin:Math.sin(j * 2 * Math.PI /180)});
			trace("setDataFrame!");
		}		
		public function onStatus(e :NetStatusEvent):void
		{
			trace(e.info.code);
			switch(e.info.code)
			{
				case "NetConnection.Connect.Success":
				ns=new NetStream(nc);
 
			//	ns.attachAudio(Microphone.getMicrophone());
			//	ns.attachCamera(Camera.getCamera());
				ns.addEventListener(NetStatusEvent.NET_STATUS,onStatus);
				ns.client=this;
				ns.bufferTime=4;
				ns.publish("CuePoints","record");
				break;
				case "NetStream.Publish.Start":
 
				setInterval(onAnimationTrigger,33);
				setTimeout(onStop,60000);
				break;
			}		
		}
	}
}
RedSandy 0.9

Alright, its time for an update. Introducing RedSandy 0.9

What we have here folks is a red5 handler and more importantly, a new class SharedTransformGroup. It allows clients to share the position and animation of a transform group using the power of red5. Now, you can either try to set up a 'real time' data model like this first example attempts, or you can set up turn based input.

So, you dream up a way to use it and make your regular sandy world. You will need a client to respond to server and remote-client generated events. This client object will need to implement the IRedSandy interface.

		/**
		 * Server initiated call to retrieve this instance's cooridinates. 
		 * It is called at the server-granularity setting(Red5 java setting), or as fast as your network and application can respond, whichever is greator.
		 * It is a timely moment to process user input.  
		 */
		function onInput(obj:SharedTransformGroup):void;
		/**
		 * Another instance has been created.
		 * @param obj user info
		 * @param group the new user's SharedTransformGroup
		 * 
		 */		
		function onJoined(obj:Object,group:SharedTransformGroup):void
		/**
		 * This client's instance has been created.
		 * @param obj The SharedTransformGroup
		 * 
		 */		
		function onCreated(obj:SharedTransformGroup):void
		/**
		 * Another instance has been destroyed. 
		 * @param obj The user's info
		 * 
		 */		
		function onLeave(obj:Object):void
		/**
		 * In response to a user generated 'sendEvent' call.
		 * @param obj the Object sent ny the other client.
		 * 
		 */		
		function onEvent(obj:Object):void

Here is an example swf application which produces a sphere for each connected user and allows slight, ever so slight, navigation of the spheres.

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.NetStatusEvent;
	import flash.ui.Keyboard;
	import flash.utils.setTimeout;
 
	import sandy.core.Scene3D;
	import sandy.core.scenegraph.Camera3D;
	import sandy.core.scenegraph.Group;
	import sandy.primitive.Sphere;
	import sandy.redSandy.IRedSandy;
	import sandy.redSandy.RedSandy;
	import sandy.redSandy.SharedTransformGroup;
	import sandy.redSandy.Connection;
 
	[SWF(width="400",height="400")]
	/**
	 * 
	 * @author Andy Shaules
	 * 
	 */	
	public class Redsandy extends Sprite implements IRedSandy
	{
		private var c:RedSandy;
		private var world:Scene3D;
		private var camera:Camera3D;
		private var group:Group;	
		private var keys:Object=new Object();
		public var clientData:Object=new Object ();
 
		public function Redsandy()
		{	//crank it up
			stage.frameRate=40;
 
			// make the connection.
			c=new RedSandy();			
			c.client=this;
			c.netConnectionEvents.addEventListener(NetStatusEvent.NET_STATUS,onStatExample);
			c.connect("rtmp://localhost:1935/sandy/room","hello","sandy");
 
 
 
			// make the sandy world
			camera = new Camera3D(400,400);
        	group=new Group();
        	world =new Scene3D('avatar'+Math.random(),this,camera, group);
        	world.camera.near=.01; 
			world.rectClipping=false;
			camera.z=-300;
 
			// add the example application funcitons
			stage.addEventListener(KeyboardEvent.KEY_DOWN,onKey);
			stage.addEventListener(KeyboardEvent.KEY_UP,onKey);
			addEventListener(Event.ENTER_FRAME,onFrame);
 
		}
 
		/**
		 * Server initiated call to retrieve this instance's cooridinates. 
		 * It is called at the server-granularity setting, or as fast as your network and application can respond, whichever is greator.
		 * It is a timely moment to process user input.  
		 */
		public function onInput(myPosition:SharedTransformGroup):void
		{
 
			if(isDown(Keyboard.UP))
			{
				myPosition.m_nVelocityX=myPosition.m_nVelocityX-1;
			}
			else if(isDown(Keyboard.DOWN))
			{
				myPosition.m_nVelocityX=myPosition.m_nVelocityX+1;
			}
			if(isDown(Keyboard.LEFT))
			{
				myPosition.m_nRotateVelocityY=myPosition.m_nRotateVelocityY-1;
			}
			if(isDown(Keyboard.RIGHT))
			{				
				myPosition.m_nRotateVelocityY=myPosition.m_nRotateVelocityY+1;
			}						
		}
 
		/**
		 * Called from another user's sendEvent function call.
		 * @param obj
		 * 
		 */		
		public function onEvent(obj:Object):void
		{
			trace(obj.value)
		}
 
		/**
		 * Called when user joins the room.
		 */
		public function onJoined(obj:Object,group:SharedTransformGroup):void
		{
			trace("joined by "+obj.id);
 
			var b:Sphere=new Sphere('sphere'+obj.id,25);
			group.addChild(b);
			world.root.addChild(group);
		}
 
		/**
		 * Called when user leaves the room.
		 */
		public function onLeave(obj:Object):void
		{
			world.root.removeChildByName(obj.id);
		}
 
		/**
		 * Here is this instance's SharedTransformGroup
		 * */
		public function onCreated(obj:SharedTransformGroup):void
		{	
			clientData.id=obj.name;
			var b:Sphere=new Sphere('sphere',25);
			obj.addChild(b);
			world.root.addChild(obj);
		}
		/**
		 * If using the velocity properties of the SharedTransformGroup, 
		 * you must call 'SharedTransformGroup.initiateRender()'.
		 * Test App implementation 
		 * @param e
		 * 
		 */		
		private function onFrame(e:Event):void
		{
			SharedTransformGroup.initiateRender();
			world.render();			
		}
		/**
		 * Test App implementation 
		 * @param ns
		 * 
		 */				
		private function onStatExample(ns:NetStatusEvent):void
		{
			trace(ns.info.code);
			if(ns.info.code=='NetConnection.Connect.Success')
			{	
				// Run some tests.
 
				// set a single property
				setTimeout(setPropertyExample,4000);
 
				// retrieve it
				setTimeout(getPropertyExample,5000);
 
				// get another user property (in this test case, our own.)
				setTimeout(getUserPropertyExample,6000);
 
				// get all my properties
				setTimeout(getAllPropertiesExample,7000);
 
				// Send to other clients. This instance will not get this event.
				setTimeout(sendEventExample,8000);
			}	
		}
 
		/**
		 * Test App implementation
		 * 
		 */		
		public function setPropertyExample():void
		{
			c.setProperty("test","success",onResult);
			function onResult(obj:Object):void
			{
				trace(obj);					
			}			
		}
 
		/**
		 * Test App implementation 
		 * 
		 */		
		public function getPropertyExample():void
		{
			c.getProperty("test",onResult);
			function onResult(obj:Object):void
			{
					trace(obj);
					clientData["test"]=obj;							
			}			
		}
 
		/**
		 * Test App implementation 
		 * 
		 */				
		public function getAllPropertiesExample():void
		{
				c.getAllProperties(onResult2);
				function onResult2(obj2:Object):void
				{
					clientData=obj2;
					trace(obj2.test);						
				}			
		}
 
		/**
		 * Test App implementation 
		 * 
		 */		
		public function getUserPropertyExample():void
		{
			c.getUserProperty(clientData.id,"test",onResult);
			function onResult(obj:Object):void
			{
					trace(obj);						
			}			
		}
 
		/**
		 * Test App implementation 
		 * 
		 */				
		public function sendEventExample():void
		{
 
			c.sendEvent({type:"event.test", value:"success"});		
		}
 
		/**
		 * Test App implementation 
		 * @param ke
		 * 
		 */				
		private function onKey(ke:KeyboardEvent):void
		{
			switch(ke.type)
			{
				case KeyboardEvent.KEY_UP:				
 
				keys[ke.keyCode.toString()]="up";
				break;
				case KeyboardEvent.KEY_DOWN:
 
				keys[ke.keyCode.toString()]="down";
				break;
			}			
		}
 
		/**
		 * Test App implementation 
		 * @param val
		 * @return 
		 * 
		 */		
		public function isDown(val:uint):Boolean
		{
			if(keys[val]=="down")
			{
				return true;
			}
			return false;
		}
 
 
 
	}
}

Until next time...

rsbsmall.jpg