Tutorials:Streaming and database connection with red5 media server

From Red5Tutorials

Jump to: navigation, search

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.

Related Links

Personal tools