Tutorials:Streaming and database connection with red5 media server
From Red5Tutorials
By Milan Toth
Milan Toth is the Chief Flash Developer of Jasmin Media Group, he created one of the world's biggest flash media server system, check http://livejasmin.com and subbrands. He loves Eclipse and OS X, AS3 and JAVA, sci-fi and horror, metal and electronic.
Part One - The server application
Let's create an application based on the previous article. We will log connecting client’s ip, referrer and connection time to a database, then send back a list of the avaiable videos based on the database to the client, and 20 seconds after the client started playing a stream disconnect client because time is up.
We need a database first, I used mySQL 5.0, download and install it from here. For administration, use its own admin tool, or download phpMyAdmin from here.
Create a new table named red5first_users in the test database. In this table, create four fileds: counter( int , auto increment, primary ), ip ( varchar 256 ) . referrer ( varchar 256 ) , flashversion ( varchar 256 ) , connection ( double ). Then create a new table named red5first_streams with four fields: counter ( int , auto increment , primary ) , id ( varchar 256 ) , filename ( varchar 256 ) , duration ( integer 11 ).
After this download the java mysql driver from mysql home. Copy mysql-connector-java*.jar to the Eclipse project root. Switch back to Eclipse, Project menuitem -> Properties -> Build Path -> Libraries -> Add External JARs, and choose the connector java from project root.
We also need to copy the driver to red5/lib, and export it to the java classpath, so the red5 application will reach it.
On os x, it looks like this:
export CLASSPATH=$CLASSPATH:/Applications/Red5/lib/mysql-connector-java-5.0.6-bin.jar
Application code:
package com.milgra;
// amf object
import org.red5.io.utils.ObjectMap;
import java.util.Map;
import java.util.Vector;
import java.util.Iterator;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.red5.server.api.IScope;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.stream.ISubscriberStream;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.api.scheduling.IScheduledJob;
import org.red5.server.api.scheduling.ISchedulingService;
import org.red5.server.adapter.ApplicationAdapter;
// IScheduledJob implementation is needed for timing
public class Application extends ApplicationAdapter implements IScheduledJob
{
//dbase adress
private static final String url = "jdbc:mysql://127.0.0.1:3306/test";
private static final String user = "youruser";
private static final String pass = "yourpass";
//logger
private static final Log log = LogFactory.getLog( Application.class );
// we want class initialization at start, so its static
static
{
try
{
Class.forName("com.mysql.jdbc.Driver");
}
catch ( ClassNotFoundException exception )
{
log.error( "ClassNotFoundException " + exception.getMessage( ) );
}
}
public boolean appStart ( IScope scope )
{
//start timer, cheking user presence every second
addScheduledJob( 1000 , this );
return true;
}
public void appStop ( IScope scope )
{
log.info( "Red5First.appStop" );
}
public boolean appConnect( IConnection conn , Object[] params )
{
log.info( "Red5First.appConnect " + conn.getClient().getId() );
// getting client parameters
Map properties = conn.getConnectParams();
//connection time
Long stamp = System.currentTimeMillis( );
// client ip
String ip = (String)conn.getRemoteAddress( );
// agent
String agent = (String)properties.get( "flashVer" );
// referrer
String referrer = (String)properties.get( "swfUrl" );
// this will be our stream list
Object[ ] streamList = { };
try
{
// trying to connect
Connection mySQLConn = DriverManager.getConnection( url , user , pass );
// we put the parameters into the dbase
Statement sttmnt = mySQLConn.createStatement( );
String insert = "INSERT INTO "
+ "`test`.`red5first_users` "
+ "(`counter`,`ip`,`referrer`,`flashversion`,`connection`)"
+ "VALUES(NULL,'" + ip + "','" + referrer + "','" + agent + "','" + stamp + "')";
// execute query
sttmnt.executeUpdate( insert );
// getting stream list
String query = "SELECT * FROM "
+ "`red5first_streams`";
ResultSet streamSet = sttmnt.executeQuery( query );
// converting resultSet to ObjectMap-based Vector
Vector results = new Vector( );
int counter = 0;
while ( streamSet.next( ) )
{
int duration = streamSet.getInt( “duration” );
String id = streamSet.getString( “id” );
String filename = streamSet.getString( “filename” );
ObjectMap oneRow = new ObjectMap( );
oneRow.put( “id” , id );
oneRow.put( “filename” , filename );
oneRow.put( “duration” , duration );
results.add( oneRow );
counter++;
}
//creating streamList
streamList = new Object[counter];
for ( int a = 0 ; a < results.size( ) ; a++ )
streamList[a] = results.get( a );
}
catch ( SQLException exception )
{
log.error( "SQLException at appConnect: " + exception.getMessage( ) );
}
// if streamlist is not empty, sending it to client
if ( streamList.length != 0 )
{
IServiceCapableConnection iconn = (IServiceCapableConnection)conn;
iconn.invoke( "message" , new Object[] {streamList} );
}
IClient client = conn.getClient( );
// setting client timestamp to 0, because it doesn't started any stream
client.setAttribute( "stamp" , new Long( 0 ) );
return true;
}
public void appDisconnect( IConnection conn , Object[] params )
{
log.info( "Red5First.appDisconnect " + conn.getClient().getId() );
}
public void streamSubscriberStart ( ISubscriberStream stream )
{
log.info( "Red5First.streamSubscriberStart" );
// somebody started a stream
IConnection conn = stream.getConnection( );
IClient client = conn.getClient( );
Long stamp = (Long)client.getAttribute( "stamp" );
// if client isn't already watching, setting stamp
if ( stamp == 0 ) client.setAttribute( "stamp" , System.currentTimeMillis( ) );
}
public void streamSubscriberStop ( ISubscriberStream stream )
{
log.info( "Red5First.streamSubscriberStop" );
}
// this function is called every second by SchedulingService
public void execute ( ISchedulingService isservice )
{
log.info( "Red5First.execute" );
long now = System.currentTimeMillis( );
Iterator it = this.getScope( ).getClients( ).iterator( );
// iterating through clients
while ( it.hasNext() )
{
IClient client = ( IClient )it.next( );
long stamp = ( Long ) client.getAttribute( "stamp" );
// if client is watching a stream
if ( stamp > 0 )
{
long duration = ( now - stamp ) / 1000;
// if time is up, disconnect
if ( duration > 20 ) client.disconnect( );
}
}
}
}
And that's it.
Create a directory in red5/webapps/firstapp called streams, and copy a few flv streams here, then fill up red5first_streams table in the database with the attributes of these streams. Our application will reach them by default, because if we check WEB-INF/web.xml:
<security-constraint>
<web-resource-collection>
<web-resource-name>Forbidden</web-resource-name>
<url-pattern>/streams/*</url-pattern>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
This folder will be enabled by Tomcat. After Eclipse auto-built the application, copy your project's WEB-INF under red5/webapps/firstapp, and you can start the server.
Part Two - The client application
Let's create client side, just edit Red5FirstClient in Eclipse/Flex from the previous article.
package
{
import flash.net.NetStream;
import flash.net.NetConnection;
import flash.net.ObjectEncoding;
import flash.text.TextField;
import flash.media.Video;
import flash.events.MouseEvent;
import flash.events.NetStatusEvent;
import flash.display.Sprite;
import flash.display.MovieClip;
public class Red5FirstClient extends Sprite
{
private var nc:NetConnection;
private var ns:NetStream;
private var items:Array;
private var video:Video;
public function Red5FirstClient()
{
nc = new NetConnection( );
nc.client = this;
nc.objectEncoding = ObjectEncoding.AMF0;
nc.addEventListener( NetStatusEvent.NET_STATUS , netStatus );
nc.connect( "rtmp://localhost/firstapp" , true );
//we store the buttons representing streams here
items = [ ];
// creating a video instance
video = new Video( );
//place it to the right
video.x = 70;
//attach it to the display list
addChild( video );
}
private function netStatus ( event:NetStatusEvent ):void
{
trace( "netStatus: " + event.info.code );
}
public function message ( message:Object ):void
{
trace( "message " );
//red5first will send stream list here, and we create a button for every stream
for ( var a:* in message )
addItem( message[a] );
}
// creating buttons
public function addItem ( properties:Object ):void
{
trace( "addItem" );
// a button is 20 px height, and we need 2 px row between buttons
var height:Number = items.length * 22;
var newItem:MovieClip = new MovieClip( );
// button label
var newField:TextField = new TextField( );
newField.width = 70;
newField.height = 20;
// setting stream id as button label
newField.text = properties.id;
// storing the other properties in the button
newItem.id = properties.id;
newItem.filename = properties.filename;
newItem.duration = properties.duration;
newItem.y = height + items.length * 2;
// drawing background
newItem.graphics.beginFill( 0x00ff00 , 1 );
newItem.graphics.drawRect( 0 , 0 , 70 , 20 );
newItem.buttonMode = true;
newItem.mouseChildren = false;
// mouseclick event
newItem.addEventListener( MouseEvent.CLICK , playItem );
//attaching label to button
newItem.addChild( newField );
addChild( newItem );
items.push( newItem );
}
public function playItem ( event:MouseEvent ):void
{
trace( "playItem: " + event.target );
// is a stream is already playing, close it
if ( ns != null ) ns.close( );
// new stream
ns = new NetStream( nc );
// onMetaData comes here
ns.client = this;
//starting stream
ns.play( event.target.filename );
//attach stream to video
video.attachNetStream( ns );
}
public function onMetaData ( message:Object ):void
{
trace( "onMetaData: " );
for ( var a:* in message ) trace( a + " : " + message[a] );
}
}
}
Start red5, than run the client, if there are records in the database, buttons will appear in the client, and you can choose and play them, and playing stops after 20 seconds.
After this part creating a startup script is a very good idea, which deletes the previous app from red5/webapps/firstapp/WEB-INF, copies EclipseWorkSpace/Red5FirstApp/WEB-INF under red5/webapps/firstapp, then starts red5.
On OS X, it looks like this:
#!/bin/bash rm -r /Applications/Red5/webapps/firstapp/WEB-INF cp -r /Users/milantoth/Store/Development/Workspace/Red5FirstApp/WEB-INF /Applications/Red5/webapps/firstapp cd /Applications/Red5 ./red5.sh
A batch file in windows looks similar to this:
:: Remove the existing directory rd /S /Q "C:\Program Files\Red5\webapps\firstapp\WEB-INF" :: Create a new one md "C:\Program Files\Red5\webapps\firstapp\WEB-INF" :: Copy files into it xcopy /E /H "C:\Documents and Settings\username\workspace\firstapp\WEB-INF" "C:\Program Files\Red5\webapps\firstapp\WEB-INF"
And that's all. Knowing this you can go on with red5 and flash.

