JUV RTMP Client (J2ME edition) Developer Guide

Version: 1.1


Table of Contents

1. Introduction
2. Getting Started
3. Architecture
4. Usage example
5. FAQ

List of Examples

3.1. IMethodInvoker implementation example
3.2. IObjectCreator implementation example
4.1. Set license key
4.2. Create and configure NetConnection
4.3. Connect to the server and invoke server method
4.4. Connect to the remote shared object
4.5. Publish audio source as live stream


Chapter 1. Introduction

JUV RTMP Client (J2ME edition) (so-called J2ME RTMP client) is a lightweight library (200KB jar) that provides RTMP/RTMPT-enabled server access API for J2ME midlets.

Features:

  • RTMP/RTMPT protocols support (both AMF0 and AMF3 encodings)
  • remote method invocation
  • remote shared object management
  • play and publish audio/video streams
  • ActionScript flash.net.* alike classes and methods
  • MIDP 2.0, CLDC 1.1 compatible

Related:
JUV RTMP Client - J2SE RTMP client
JUV RTMP Researcher - RTMP debug tool
JUV RTMP Tester - RTMP functional/regression/load testing library


Chapter 2. Getting Started

JUV RTMP Client (J2ME edition) library has a 2 jars: full version and lite version (no streaming support).

It requires the license key: either Evaluation Key (get it) or Developer Key (buy it).


Chapter 3. Architecture

JUV RTMP Client (J2ME edition) API is very similar to the API provided by Adobe® in the ActionScript's flash.net.* package.
There are 3 core classes defined in com.smaxe.me.uv.client package:

  • NetConnection is responsible for configuring/establishing the connection, remote method invocation
  • NetStream is a stream management class
  • SharedObject is a shared object management class

Connection configuration parameters are defined in NetConnection.Configuration class.
These are:
  • RECEIVE_BUFFER_SIZE - 'receive' buffer size (default: 8 * 1024 bytes) : this buffer is used by protocol
  • SEND_BUFFER_SIZE - 'send' buffer size (default: 8 * 1024 bytes) : this buffer is used by protocol
  • STREAM_BUFFER_SIZE - stream buffer size (default: 32 * 1024 bytes) : this is a lazily initialized buffer to store incoming audio/video packets. It is created only if a stream is played
  • HANDSHAKE_TIMEOUT - timeout for the handshake phase of the protocol (in seconds) (default: 30 seconds)
  • INACTIVITY_TIMEOUT - timeout for rtmp protocol connection (in seconds), i.e. if data is not received during this timeout, the connection is considered disconnected (default: -1 seconds (indefinite))
  • FPAD, PAGE_URL, SWF_URL, FLASH_VER, VIDEO_CODECS, AUDIO_CODECS, VIDEO_FUNCTION - collection of parameters sent to the server during connection phase
  • ENABLE_ACKNOWLEDGEMENT_EVENT_NOTIFICATION - 'Acknowledgement' event notification flag (default: false).
  • WINDOW_ACKNOWLEDGEMENT_SIZE - server will send 'Acknowledgement' event every N read bytes (default: 128 * 1024)
  • METHOD_INVOKER - an instance of com.smaxe.me.uv.invoker.IMethodInvoker
  • OBJECT_CREATOR - an instance of com.smaxe.me.uv.amf.IObjectCreator
  • LOGGER - an instance of com.smaxe.me.logger.ILogger
  • CONNECTION_PARAMETERS - connection parameters : socket://host:port[parameters], for example, ";interface=wifi;deviceside=true" for BlackBerry devices
  • SOCKET_CONNECTION_DELAY - socket connection delay : socketConnection.setSocketOption(SocketConnection.DELAY, [value]) (default: 0)
  • SOCKET_CONNECTION_KEEP_ALIVE - socket connection delay : socketConnection.setSocketOption(SocketConnection.KEEP_ALIVE, [value]) (default: 1)
  • SOCKET_CONNECTION_LINGER - socket connection delay : socketConnection.setSocketOption(SocketConnection.LINGER, [value]) (default: 5)
  • SOCKET_CONNECTION_RCVBUF - socket connection delay : socketConnection.setSocketOption(SocketConnection.RCVBUF, [value]) (default: 8 * 1024)
  • SOCKET_CONNECTION_SNDBUF - socket connection delay : socketConnection.setSocketOption(SocketConnection.SNDBUF, [value]) (default: 8 * 1024)
  • USE_HTTP_CONNECTION - use HttpConnection for the RTMPT protocol flag (default: false)

com.smaxe.me.uv.invoker.IMethodInvoker

Every method invoked by the server is processed by the IMethodInvoker instance. The default implementation (com.smaxe.me.uv.invoker.support.MethodInvoker) logs method invocation events to the System.out.


public interface IMethodInvoker
{
    /**
     * Invokes method with the args of the object o.
     * The result is returned through the {@link ICallback callback}.
     * 
     * @param o object which method is invoked
     * @param method method to invoke
     * @param callback callback to receive invocation result
     * @param args method arguments
     */
    public void invoke(final Object o, final String method, final ICallback callback, final Object[] args);
}

Example 3.1. IMethodInvoker implementation example


public class MyMethodInvoker extends Object implements IMethodInvoker
{
    // note: this method is invoked in the Dispatch-Thread
    public void invoke(final Object o, final String method, final ICallback callback, final Object[] args)
    {
        if (o instanceof NetConnectionCallback)
        {
            final NetConnectionCallback netConnectionCallback = (NetConnectionCallback) o;

            if ("test".equals(method))
            {
                 final boolean result = netConnectionCallback.test();
                 
                 callback.onResult(new Boolean(result));
            }
        }
    }
}

com.smaxe.me.uv.amf.IObjectCreator

Every custom Java object that is sent/received to/from the server must be presented as ClassObject, i.e. class name + {property : property value} hashtable. The IObjectCreator implementation is responsible for the such transformation.


public interface IObjectCreator
{
    /**
     * Converts {@link ClassObject co} to the custom type object.
     * 
     * @param co ClassObject instance
     * @return custom object
     */
    public Object toObject(final ClassObject co);
    
    /**
     * Converts {@link Object o} to the {@link ClassObject} instance.
     * 
     * @param o object to represent as ClassObject
     * @return ClassObject instance
     */
    public ClassObject toClassObject(final Object o);
}

Example 3.2. IObjectCreator implementation example


public class CustomClass extends Object
{
    public int field1;
    public String field2;
}

public class ObjectCreator implements IObjectCreator
{
    public ClassObject toClassObject(final Object o)
    {
        if (o instanceof CustomClass)
        {
            final CustomClass cc = (CustomClass) o;

            Hashtable propertyValues = new Hashtable();
            
            propertyValues.put("field1", cc.field1);
            propertyValues.put("field2", cc.field2);
            
            return new ClassObject("CustomClass", propertyValues);
        }
    }

    public Object toObject(final ClassObject co)
    {
        if ("CustomClass".equals(co.className))
        {
            CustomClass cc = new CustomClass();
            
            cc.field1 = (Integer) co.propertyValues.get("field1");
            cc.field2 = (String) co.propertyValues.get("field2");

            return cc;
        }
    }
}

com.smaxe.me.logger.ILogger

The library doesn't use any specific logging system. It provides a simple interface that lets you either integrate the library with any logging library or use ILogger implementations provided in the com.smaxe.me.logger.support package.


public interface ILogger
{
    /**
     * Logs the message.
     * 
     * @param level message level
     * @param message message to log
     * @param t thrown exception, null if the exception is not thrown
     * @param args optional arguments
     */
    public void log(final int level, final String message, final Throwable t, final Object[] args);
} 
 

Important

Cause of the asynchronous nature of client-server communication the methods invoked by the server are processed in the Dispatch-Thread (similar to the Event-Dispatch-Thread in Swing). This means that you have to process listener notifications in the separate executor thread.


Chapter 4. Usage examples

Example 4.1. Set license key

 
 com.smaxe.me.uv.client.License.setKey("00000-00000-00000-00000-00000");
 

Example 4.2. Create and configure NetConnection

 
 NetConnection connection = new NetConnection();
 // configure 
 connection.configuration().put(NetConnection.Configuration.INACTIVITY_TIMEOUT, new Integer(10) /*seconds*/);
 connection.configuration().put(NetConnection.Configuration.PAGE_URL, "www.myhost.com/app");
 // etc...

Example 4.3. Connect to the server and invoke server method


 NetConnection connection = new NetConnection();

 // configure connection (if necessary)

 // set callback object
 connection.client(new Object()
 {
     // note: this method is invoked in the Dispatch-Thread
     public boolean test()
     {
         System.out.println("NetConnectionCallback#test");
         
         return true;
     }
 });
 
 // add event listener to get notified about connection status change
 connection.addEventListener(new NetConnection.ListenerAdapter()
 {
     // note: this method is invoked in the Dispatch-Thread
     public void onNetStatus(final NetConnection source, final Hashtable info)
     {
         final Object code = info.get("code");
         
         if (NetConnection.CONNECT_SUCCESS.equals(code))
         {
             System.out.println("Connected to the server!");
         }
     }
 });
 
 connection.connect("rtmp://localhost:1935/app", new Object[] { "arg1", "arg2"} /*arguments*/);
 
 // wait till NetConnection.CONNECT_SUCCESS message is received
 // ... waiting ...
 
 // invoke server method
 connection.call("testConnection", new com.smaxe.me.uv.Responder()
 {
     // note: this method is invoked in the Dispatch-Thread
     public void onResult(final Object result)
     {
         System.out.println("Method testConnection result: " + result);
     }
     
     // note: this method is invoked in the Dispatch-Thread
     public void onStatus(final Hashtable status)
     {
         System.out.println("Method testConnection status: " + status);
     }
 }, new Object[] { "arg3", "arg4"} /*arguments*/);
 

Example 4.4. Connect to the remote shared object

 
 NetConnection connection = new NetConnection();
 
 // ... configure and connect ... 
 
 SharedObject so = new SharedObject(name);
 
 so.addEventListener(new SharedObject.ListenerAdapter()
 {
     // note: this method is invoked in the Dispatch-Thread
     public void onSync(SharedObject source, final Vector changeList)
     {
         for (int i = 0, n = changeList.size(); i less n; i++)
         {
             final SharedObject.Change change = changeList.elementAt(i);
             
             switch (change.code)
             {
                 case SharedObject.Change.CONNECT:
                 {
                     // connected to the remote shared object
                 } break;
                 case SharedObject.Change.CHANGE:
                 {
                     // change.attribute data was changed from the change.oldValue
                     // to the change.newValue
                 } break;
                 case ISharedObject.Change.STATUS:
                 {
                     // shared object status notification
                 } break;
             }
         }
     }
 });
 
 so.connect(connection, name);
 

Example 4.5. Publish audio source as live stream

 
 public final class MyMicrophone extends AbstractMicrophone
 {
     // starts microphone capture
     public void start()
     {
         new Thread(new Runnable()
         {
             while (true)
             {
                 byte[] data = getAudioData(); // returns audio data captured from your source
                 
                 fireOnAudioData(new MediaDataByteArray(33 /*relative time*/, new ByteArray(data)));
                 
                 try
                 {
                     Thread.sleep(33);
                 {
                 catch (Exception e) {}
             }
         }).start();
     }
 }
 
 NetConnection connection = new NetConnection();
 
 // ... configure and connect ... 
 
 NetStream stream = new NetStream(connection);
 
 stream.addEventListener(new NetStream.ListenerAdapter()
 {
     @Override
     public void onNetStatus(final NetStream source, final Map info)
     {
         final String code = (String) info.get("code");
         
         if (NetStream.PUBLISH_START.equals(code))
         {
         }
         else
         if (NetStream.UNPUBLISHED_SUCCESS.equals(code))
         {
         }
     }
 });
 
 MyMicrophone mic = new MyMicrophone();
 
 stream.attachAudio(mic);
 
 stream.publish("streamName", NetStream.LIVE);
 
 mic.start();
 


Chapter 5. FAQ

What does "play and publish audio/video streams" mean?

The library provides protocol layer implementation only and doesn't contain any codec implementation. The phrase means that you'll get encoded audio/video data for playing it, i.e. you need codecs to decode the audio/video data stream, and you need to encode your data to publish it to the server.

Ask your question