|
 |
Milenia Grafter Server
The 64 Kbyte Flash Media Server
Copyright 2007 by Milan Toth
If you like this product, please donate to help me make it better.
Milenia Grafter Server is a very simple and powerful open source flash media server primarly created for my upcoming game. I share it hoping it will be useful for the flash community.
Check Milenia Grafter Chat running on the server.
Download:
Features:
- Live Audio/Video streaming
- Live Stream Recording
- Powerful stream access control
- Lightweight client - server communication
- Client mode - you can connect to any kind of flash media server
- Server to server Data/Audio/Video communication - clustering
- Low memory requirement
- Admin console
System Requirements:
- Any system with Java Virtual Machine 1.5
Performance:
System: Mac OS X 10.4 Desktop, Intel Core Duo, 1.8 Ghz, 1 Gb RAM
clients connected : 803
bandwidth : 240 Megabits
cpu load 3.42
usememory used: 180 Megabyte
Documentation
1. About the server
1.1 The server
1.2 The license
1.3 The author
1.4 Contact
1.6 Softwares used
1.7 Thanksgiving
2. Running the server
2.1 First run
2.2 Admin console
2.3 Stress test
3. Programming the server
3.1
Custom applications
3.2 IApplication interface
3.3 Client class
3.4 Wrapper class
3.5
Utilities
3.6 Streams
3.7 Stream Events
3.8 Status Events
3.9 Invoke Events
3.10 Quick examples
3.11 Custom Application Tutorial
3.12 Demo Applications
1. About the server
1.1 The server
Milenia Grafter Server is an open source flash media server primarly created for my upcoming game. Simplicity was the only keyword during its creation, so i have not implemented any useless feature from other flash media server implementations. There aren't :
- shared objects - over a hundred connected clients shared object syncronization just slows down rtmp traffic and/or wrecks down the server
- virtual hosting - why host a free server? :D
- complex amf data types
- huge and complicated frameworks
- huge, good-for-nothing config files - there is no config file :D
My first experience with a flash communication server was in early 2005. For two years i've been programming Flash Comm/Flash Media server client/application systems, and during this experience ( nightmare ) i've dreamt about a simple, good and easily programmable media server. Then Red5 and Wowza came out, and none of them robbed my heart. So i've decided to create my own flash media server, and that's how Milenai Grafter Server was born. It first came into my mind in july 2007, when i've written a simple RTMP handshaker to test how easily can a referrer be faked. And finally, in the last week of September i've jumped in, and after two months of hard work the beta versions of Milenia Grafter Server and Clients were ready.
1.2 The license
Milenia Grafter Server
Copyright (c) 2007 by Milan Toth. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1.3 The author
My name is Milan Toth, I'm a java/flash developer living in Hungary, Budapest. I'm connected to programming since my first home computer, and trough basic, pascal, and c i've settled at java and actionscript.
Employment
- 2005 - 2007 Chief Flash Developer of Jasmin Media Group
- design and creation one of the worlds biggest clustered flash media server system with live streaming and video-on-demand, full-scale monitoring and skinnable and multi-lingual applets
- 2001 - 2005 Freelance Developer
- dynamic sites and on-line games
Publications
1.4 Contact
mail : milgra at milgra dot com
skype : milgra
If you find a bug, or know a way how to make Milenia it simpler/faster, please contact me. If you want a really optimized application or client-application system for milenia directly from the creator, contact me at milgra at milgra dot com.
1.5 Softwares used
JRE 1.5 - no words for it :D
OS X 10.4 - it is REALLY the most advanced operating system of the world
OS X Calculator - the best hexa/octa/binary/decimal calculator, try pressing the green plus on the top left, ehehe :D
Eclipse 3.2 - with
- XMLBuddy plugin
- Flex Builder plugin
- AmaterasUML plugin
WireShark - a really must
0xED - like above
Safari 3 - fast as lightning
ITunes - randomized 50 Gigabytes of music all the time
1.6 Thanksgiving
My mother and my brother, for always being very glad of whatever i'm doing.
Apple, for creating human interfaced hardware and software.
My E34 BMW, for being the last classic, sportish and elegant BMW.
Open source flash community, for sharing known rtmp, amf and flv format parts.
Friends, for supporting me, and giving me power.
And myself, for always being stubborn.
2. Running the server
2.1 First run
Download and unzip Milenia Grafter Server pack. The directory structure will be the following :
applications < dir >
streams < dir >
xtras < dir >
milenia.jar
license.txt
start.bat
start.sh
"applications" contains the custom applications for the server. You have to copy your custom application jars here. "xtras" contains bonus applications, demos, sources and documentation. "streams" is the directory for recorded streams. "milenia.jar" is the server itself, "license.txt" is the GPL v2 license.
Before startup, be sure that your system has Java Runtime Environment 1.5 or higher. If not, download it from sun.
To start the server, execute startup.sh on unix systems, start.bat on win32 systems or type "java -server - jar milenia.jar" in the command line. It should start up flawlessly. You may have to use additional switches, for example, -server, or if jvm runs out of memory -Xmn -Xms -Xmx to set memory size.
To test it, open extras/milgraadmin.swf ( in a browser or a stand alone flash player ), type admin/admin for user/pass. Under applications tab, It will show one connected client ( you ) and one running application ( the admin application ).
Admin application will create a fourth directory called "admin" in Milenia's root. It will place admin.xml and log files in there. It is recommended to immediately set a new username/password in admin.xml, and you may define allowed ip addresses also.
You may try extras/milgrachat.swf, it is a simple chat application, what you can embed in your web page. If you do it, you can pass a "host" parameter to it with the server's address.
2.2 Admin Console
Admin console is the administration console for Milenia Grafter Server. At first use, type admin/admin for user/pass, and your server's address. The console will remember the last accessed address.
Graphs tab
After a succesful login, youwill find yourself at the graphs tab. These graphs shows the i/o states of the server. "Bandwidth" shows the overall, incoming, and outgoing bandwidth in Megabits/second. "Connected Clients" shows the overall, passive and active client count on the server. Active clients are the clients created by a custom application to connect to other servers. "Processing Threads" shows socket, client and stream pool processing thread count," Execution Times" show the average proces execution times of the different thread pools.
Applications tab
You can check the available applications here. They consists of the custom applications under Milenia Grafter Server / applications directory, and the running applications ( if custom application jar has been removed from the directory, but the application hasn't been unloaded ). You can check the status, connected clients and bandwidth info of an application. You can refresh application list any time, this case Milenia will reread the applicaitons directory. You have to wait for the next refresh event to see the new list. You can unload/load applications by pressing load/unload buttons.
Streams tab
All published streams on the server are listed here. If you click on a specific stream, it will show up in one of the preview windows below.
2.3 Stress Test
You can test how much load the server can handle on your system with the Stress Test part of the console. You have to click on "Start Test", and enable access to your camera and microphone. The console will publish a stream onto the server. After this you can tell the server to open dummy connections onto itself, and proxy your stream on these connections, so you can increase data throughput dramatically, and check how Milenia Grafter Server performs on you system. You can stop testing any time by clicking on "Stop Test".
If the server halts with a "java heap space" out of memory error, you have to give more memory to the virtual machine with, for example -Xmn100M -Xms500M -Xmx500M switches.
3. Programming the server
3.1 Custom Applications
You can create custom applications or application packages for Milenia Grafter Server in Java. There are three main rules for a custom application:
1. The main class of your application must be called Application
2. The main class of your application must be in a package called application
3. The main class of your application must implement IApplication interface
After your application is ready, you have to create a simple jar file from it, and copy it under Milenia Grafter Server/applications. Custom applications are dynamically loaded at startup and on the fly from admin console. Custom applications must not be in the classpath, otherwise jvm cannot reload them.
3.2 IApplication interface - com.milgra.server.IApplication
You have to implement IApplication interface in the main class of your custom application. IApplication contains three main controller methods:
onClose ( )
The server will call it when the admin unloads this application from admin console during runtime. You have to define a complete cleanup code here to avoid memory leaks.
onEnter ( Client clientX , ArrayList < Wrapper > messageX )
Client entering point, clientX is the client instance, messageX is the arraylist containing the arguments passed by the client. clientX will be in idle state, until you call the clientX.accept( ) or clientX.reject( ) methods.
onLeave ( Client clientX );
Client leaving point, do client-related cleanup here.
3.3 Client class - com.milgra.server.Client
Client class contains all the properties and methods you need for programming Milenia Grafter Server.
WARNING!!! client controllers may run in different threads!!! If you touch "common space" from your controller classes, always synchronize!!!
Properties
public long id
The unique identifier number of the client. I encourage you to use this everywhere in your server/client applications, it cannot be identical with other client's ids.
public long ping
The ping rountrip time of the client.
public long bandIN
The actual incoming bandwidth of the client in bytes per second.
public long bandOUT
The actual outgoing bandwidth of the client in bytes per second.
public long bytesIN
The received byte count by the server from this client
public long bytesOUT
The sent byte count to this client
public String ip
The ip address of the client.
public String agent
The player / server info of the client.
public String referrer
The referrer of the client.
public boolean hasVideo
If client published video data in the last 5 seconds, is true, else false.
public boolean hasAudio
If client publshed audio data in the last 5 seconds, is true, else false.
Static methods
public static Client [ ] getClients ( IApplication application )
Returns the list of the clients related to the application given as the argument.
public static String [ ] getStreams ( )
Returns the list of the published streams on the server
public static Client createClient ( IApplication application )
Creates an active client instance to connect to a server.
Methods
public void addStreamEventListener ( EventListener streamELX )
You may add a stream event listener object to the client with this method.
public void addInvokeEventListener ( EventListener invokeELX )
You may add an invoke event listener object to the client with this method.
public void addStatusEventListener ( EventListener statusELX )
You may add a status event listener object to the client with this method.
public HashMap < Double , String > getPlayedStreams ( )
Returns the list of streams played by the client
public HashMap < Double , String > getPublishedStreams ( )
Returns the list of streams published by the client
public void call ( String invokeID )
Invokes a method on client side without arguments
public void call ( String invokeID , Wrapper argumentX )
Invokes a method on client side with a wrapper as arguments
public void call ( String invokeID , ArrayList < Wrapper > argumentsX )
Invokes a method on client side with an arraylist of wrappers as argument
public void callResult ( String invokeID , Wrapper argumentX )
If a responder for a specific function is defined on client side, you have to pass back the result with function with a wrapper as argument
public void callResult ( String invokeID , ArrayList < Wrapper > argumentsX ) { }
If a responder for a specific function is defined on client side, you have to pass back the result with function with an arraylist of wrappers as argument
public void pauseStream ( double streamIDX , boolean stateX )
Un/Pauses the client's specific published stream ( if it exists )
public void recordStream ( double streamIDX , boolean stateX )
Records/stops recording the client's specific published stream ( if it exists )
public void deleteStream ( double streamIDX )
Deletes resources associated with the client's specific ( played, published ) stream.
public void enableStreamPlay ( double streamIDX , String streamNameX )
Enables playing a specific stream for the client. If you want to play a different stream than the requested for the client in this stream channel, pass streamNameX its name, otherwise the requested name.
public void disableStreamPlay ( double streamIDX , String streamNameX )
Disables playing a specific stream for the client. You have to call it as the response of a stream event. If you want to disable an existing stream, use deleteStream instead.
public void enableStreamPublish ( double streamIDX , String streamNameX , String streamModeX )
Enables publishing a specific stream from the client. If you want the stream to be published the stream with a different name or different mode than the requested, pass streamNameX and streamModeX different values.
public void disableStreamPublish ( double streamIDX , String streamNameX )
Disables publishing a specific stream from the client. You have to call it as the response of a stream event. If you want to disable an existing stream, use deleteStream instead.
public double cloneStream ( double streamIDX , String streamNameX )
Clones the client's specific stream, it will be publshed under the new name streamNameX
Passive mode only methods
public void accept ( Wrapper wrapperX )
Accepts the client, you can pass a wrapper as acception info, the client receives it as the NetStatus event's info.applicaiton parameter
public void reject ( Wrapper wrapperX )
Rejects the client, you can pass a wrapper as rejection info, the client receives it as the NetStatus event's info.application parameter
public void detach ( )
Detaches the client from the server
Active mode only methods
public void connect ( String uriX , ArrayList < Wrapper > argumentsX )
Connects the client to the remote server with address uriX, passing argumentsX as connection object
public void publishStream ( String streamNameX , String streamModeX )
Publishes a stream available in the server to the remote server
public void unpublishStream ( double streamNameX )
Unpublishes a stream published to the remote server
3.4 Wrapper class
Wrapper class is a simple value wrapper, but most of your time programming Milenia Grafter Server you will use wrappers.
As you know, java is a strictly typed language and actionscript is weakly typed. During data exchange flash can send objects and arrays containing mixed data types, and on the java side after deserialization we have to keep this state somehow in the strictly typed environment. That's why Wrapper class was born. AMF deserializer wraps every data in Wrapper objects.
public String type
can be "Null" , "Integer" , "Long" , "Double" , "String" , "Boolean" , "ArrayList" and "HashMap"
public Object value
If you don't use generics, or value is a simpler type, you can get value from this property
The following properties are evident:
public int intValue
public long longValue
public double doubleValue
public String stringValue
public boolean booleanValue
public ArrayList < Wrapper > listValue
public HashMap < String , Wrapper > hashValue
Example:
ActionScript 3 Object:
var arr = [ ];
var list = { };
list.id = "MileniaGrafter";
list.count = 45;
list.names = [ "apple" , "tomato" , "thomas" ];
arr[0] = list;
arr[1] = "StatusText";
On server side, array converted to wrappedObject :
HashMap < String , Wrapper > list = wrappedObject..get(0).listValue;
String status = wrappedObject.get(1).stringValue; // "StatusText"
String id = list.get("id").stringValue; // "MileniaGrafter"
Double count = list.get( "count" ).doubleValue; // 45
ArrayList names = list.get( "names" ).listValue;
String firstName = names.get(0).stringValue; // "apple" "tomato" "thomas"
3.5 Utilities
Under com.milgra.server.utils you can a find a few simple and useful utilities i created for milenia grafter.
Timer - com.milgra.server.util.Timer
Simple timer. You have to define an EventListener first, then instantiate the timer with an arbitray interval in milliseconds, and the eventlistener. For example:
import com.milgra.server.EventListener;
import com.milgra.server.util.Timer;
EvenListener tickEvent = new EventListener( )
{
public void onEvent( HashMap < String , Wrapper > eventX ) { tick( ); }
};
Timer timer = new Timer( 5000 , tickEvent );
timer.start( );
it will call the following function periodically in every 5 seconds:
public void tick ( )
{
System.out.println( "tick" );
}
LogWriter - com.milgra.server.util.LogWriter
Log writer. Instantiate it with the log file name, then call addLog( String log ) to add new entry. Close it with close( ). Example:
LogWriter logWriter = new LogWriter( "log.txt" );
logWriter.addLog( "First entry" );
logWriter.close( );
XMLParser - com.milgra.server.util.XMLParser
A quick and dirty xml parser. It only handles nodes without attributes, and values.
Methods:
getValue ( String path , int index )
returns the text value of the node represented by path, at index occurence.
getCount ( String path )
return the number of the childNodes of the node represented by path
Example:
example.xml =
<ids>
<id>apple</id>
<id>tomato</id>
<id>corn><id>
</ids>
XMLParser parser = new XMLParser( "example.xml" );
int count = parser.getCount( "ids.id" ); // count = 3
String text = parser.getValue( "ids.id" , 1 ); // text = "tomato"
3.6 Streams
Mileania's stream pool is shared, so every applicaiton can reach every stream published to the server
You can publihs/record/play streams from clients without access control by default. If you want to control stream access, you have to define a stream event listener in your custom application, and add it to the client with client.addStreamEventListener.
After this you will be notified about every NetStream.play and NetStream.publish event, and you have to enable or disable these requests. You can even change publishing mode with this function.
3.7 Stream Events
Stream events came in a HashMap < String , Wrapper > object. Object keys are the following:
key : "eventid" value : String, can be "play" "publish" "delete"
key : "clientid" value : Long , the id of the broadcasting client
key : "streamid" value : Double , the id of the requested stream
key : "streamname" value : String , the name of the requested stream
key : "streammode" value : String , if even is a publish event, the requested publishing mode.
3.8 Status Events
Status events came in a HashMap < String , Wrapper > object. Object keys are the following:
key : "clientid" value : Long , the id of the broadcasting client
key : "level" value : String , can be "error" , "status"
key : "code" value : String , event code
key : "description" value : String , event description
key : "application" value : Wrapper , acception/rejection result info
possible codes :
"NetConnection.Connect.Success" - active client's connection succeded
"NetConnection.Connect.Rejected" - active client's connection rejected
"NetConnection.Connect.Failed" - active client's connection failed
"NetConnection.Connect.Closed" - active client's connection closed
"Invoke.Callback.Failed" - if callResult called, but responder didn't exist for the specific function
3.9 Invoke Events
Client side invokes come in the form of HashMap < String , Wrapper > objects.
key : "clientid" value : Long , the id of the broadcasting client
key : "invokeid" value : String , the identifier of the method
key : "arguments" value : ArrayList < Wrapper > , the arguments for the method
3.10 Quick Examples
These examples are in AS3, never forget to set AMF object encoding to AMF0 on the client side, because Milenia uses AMF0.
How do I connect to Milenia Grafter server? ( Client side )
import flash.net.ObjectEncoding;
import flash.net.NetConnection;
var nc:NetConnection = new NetConnection( );
nc.objectEncoding = ObjectEncoding.AMF0; // Milenia uses AMF0 , so never forget this!!!
nc.addEventListener( NetStatusEvent.NET_STATUS , onNetStatus );
nc.connect( "rtmp://hostnam/appname" , arguments );
How do i accept a client in a custom java application? ( Client side )
public void onEnter ( Client clientX , ArrayList < Wrapper > arguments )
{
clientX.accept( );
clientX.accept( new Wrapper( "Your user id is: " + 1) );// on client side, event.info.application is "Your user id is 1";
}
How do i reject a client in a custom application? ( Client side )
public void onEnter ( Client clientX , ArrayList < Wrapper > arguments )
{
clientX.reject( new Wrapper( ) );//passing null as information object
clientX.reject( new Wrapper( "Invalid referrer") );// on client side, event.info.application is "Your user id is 1";
}
How do i disconnect a client from the server? ( Server side )
client.detach( );
How do i connect to a remote server? ( Server side )
first you have to instantiate a new Client with Client.createClient( ) static method. You have to add a statusEventListener to be notified about connection status, then you have to call Client.connect( "rtmp://hostname/appname" , arguments ); just like on client side with NetConnection. The netstatus events are the same either.
Client client = Client.createClient( this );
client.addStatusEventListener( statusEL );
client.connect( "rtmp://remotehost/remoteapp" , arguments );
How do i disconnect from another server? ( Server side )
client.detach( );
How do i send a stream to another server? ( Server side )
You have to call client.publish( streamName , streamMode ). streamName must be a published stream on the server. The stream will act as a normal stream published by a client on the other server.
How do I call a method from the client on the server? ( Client side )
nc.call( "methodName" , null , arguments );
nc.call( "methodName" , responder , arguments );
If you define a responder, you have to call Client.callResult to send back a response
How do I invoke a method from the server on the client? ( Server side )
client.call( "methodName" , arguments );
Important things again
1. Always use AMF0 ObjectEncoding on client side
2. Watch out with Client's invoke, status and stream events, they can come in different threads, remember to synchronize!!!
3.11 Custom Application Tutorial
We will a create a simple application, in which the client connects to the server, the server sends back the client's id as response, the client starts to publish a stream with this id, and the server enables the stream if the streams name contains the id, else disables it.
Donwload and unzip Milenia Grafter Server. Download and install Eclipse 3.2 ( os x ), 3.1.2 ( windows ), and Flex Builder Plugin from Adobe. Ensure that you have JRE 1.5 on your system, and JRE executables are in the path.
Start Eclipse. File menuitem -> New -> Project, select Java project, click Next, type MilGraTutorial as project name. If JRE 1.5 is not the default, select it under JRE. Click next. For default output folder, type MilGraTutorial/bin , so Eclipse will compile classes under a separate "bin" directory, and it will be easier for us to pack our classes into jar. Click on libraries tab, click Add External JARs, choose Milenia Grafter's folder, and select milenia.jar, click Open. That's all for project setup, click Finish.
MilGraTutorial project appeared in Package Explorer. File menuitem -> New -> Class.
For class name, type Application ( first letter uppercase ), and in the package field, type application ( all lowercase ). Every main custom application class must be application.Application. Click finish.
The class is created. Every main application class must implement Milenia Grafter's IApplication interface. So, under package name type "import com.milgra.server.IApplication;"
Now we have to create the three implemented functions: onEnter, onLeave, onClose, and add wanted functionality. At the end, Application class will look like this: Application.java
Let's create UserController class, it is not necessary, but i want to show you how to use multiple classes/packages as application, and i also want to show the benefits of creating a separate controller instance for every client. File menuitem -> New -> Class. For class name, type UserController, click Finish. UserController.java
Okay, we are ready with the application, now we have to compress it to a jar, and copy it under Milenia Grafter Server/applications.
Open a terminal, go in your Eclipse workspace directory, go under MilGraTutorial/bin. You see an "application" directory, where eclipse compiled our application classes. Type
jar -cf milgratutorial.jar application
And a milgratutorial.jar file appears in the directory. Copy this file under Milenia Grafter Server/applications. Start the server if it's not running, or load the application with MilGraAdmin under applications tab. ( refresh - load ).
Let's create the client. Go in Eclipse,switch to Flex development perspective, File menuitem -> New -> Actionscript project, type MilGraTutorialClient for project name, press finish. The main class will look like this: MilGraTutorialClient.as
Select MilGraTutorialClient class in package explorer, right click - run as Flex application, and the client will start streaming. Try inserting System.out.println's in the server-side code to see what is happening. Source can be find under Milenia Grafter Server/extras.
3.12 Demo Applications
These applications are demonstrating Milenia's capabilities. To catch everything, keep the terminal with Milenia's standard output next to the flash client's to see changes on both side.
MilGraDemoData - its a data exchange demo, it call invokes with various data types on the server, than the server calls invokes on client side
MilGraDemoStream - a stream demo
MilGraDemoRemote - streaming through a remote connection
|
|
|
|