Red5 Pro SharedObjects

1. Introduction

Other than being an excellent media server for streaming audio/video , Red5 Pro is also a top-notch real-time data communication server. You can build exotic applications such as real-time messaging (chat), real-time data collection (IOT sensors), real-time data processing and publishing (real-time graphs) and much more.

SharedObjects were introduced by Adobe Flash years ago. Now even as Flash has faded into the past, SharedObjects are still growing strong. In the context of a real-time media server, a SharedObject follows a pub/sub model and acts like a remote real-time bucket. As soon as you make any changes to this bucket, all subscribers will automatically get notified.

The availability of SharedObjects shows the data oriented side of Red5 Pro, which can be an ideal candidate for modern day data oriented application such as IOT systems, real-time chat applications and more. Using Red5 Pro you can build compelling communication applications between flash, HTML5 and mobile clients.

2. Types of Shared Objects

SharedObjects are generally of two types :- persistent and nonpersistent.

  • Persistent SharedObject : Retains the data content by serializing it to disk. Hence if you restart the media server, the data which was stored before the restart can be accessed once again.

  • Non-persistent SharedObject on the other hand is volatile in nature and will lose data once you restart the media server.

Each relevant platform API (Server side or Client side) provides an optional Boolean parameter in its SharedObject creation function, to specify whether a persistent SharedObject is needed or a Non-Persistent one.

Red5 Pro Shared Objects are created & managed in two ways:

Red5 Pro deals with two types of SharedObject creational patterns - client-side shared objects & server-side shared objects.

2.1 Client-Side SharedObjects

Client side SharedObjects are created and manipulated from the client side. The resources exists on the server but the client can connect to it and manipulates it using a set of API's. These are also called RSO (Remote SharedObject). Actionscript 3 is a client side language for Adobe flash, which provides extensive APIs to work with SharedObjects. Similarly Red5 Pro HTML5 SDK & Red5 Pro Mobile SDKs (IOS/Android) provide API's to create and manipulate remote SharedObjects from the client side. A RSO created from the client side is auto terminated once there are no more connections to it.

Client-Side SharedObjects use cases:

  • When SharedObject's life depends on client existence
  • When server does not need to manipulate the SharedObject

2.2 Server-Side SharedObjects

Server-side SharedObjects are created and manipulated on the server side using the Red5 Pro API. A Server side SharedObject is retained by the server and is not affected by the number of client connections to it. Optionally it is possible for a client to connect to a SharedObject initialized by the server.

This article discusses server side SharedObject capabilities of the Red5 Pro API

Server-Side SharedObjects use cases:

  • When SharedObject's life does not depends on client existence such as
    • Synchronizing clients
    • Pushing data to clients
    • Server-side data storage

3. Working with client-side SharedObjects (RSO)

As mentioned, client-side SharedObjects can be created using the client platform's provisioned API. When working with the Red5 Pro media server you can use Actionscript API or Red5 Pro HTML5 SDK or Red5 Pro Mobile SDK (Android/IOS) to create and manipulate client side SharedObjects.

The code snippet given below shows how we connect to the media server using a NetConnection (Actionscript 3 API). Once we are connected to the server, we initialize a Remote SharedObject over the existing connection and attach a change listener to it. The change handler is triggered whenever there a change made to the SharedObject’ data, either by a client or by the server.

Creating SharedObjects using ActionScript

var nc:NetConnection = new NetConnection();
nc.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
nc.connect("rtmp://localhost/live");

function onNetStatus(event:NetStatusEvent):void
{
var nc:NetConnection = event.target as NetConnection;

if(event.info.code == "NetConnection.Connect.Success")
{
SharedObject so = SharedObject.getRemote("shObj",nc.uri);
so.addEventListener(SyncEvent.SYNC,syncHandler); //add event listener for SharedObject
so.connect(nc);
}
}

function syncHandler(event:SyncEvent):void
{

}

Updating a property

You can update attributes on the SharedObject’s data from the client side, using the ‘setProperty’ method.

function changeValue(SharedObject so, String property, newValue:*)
{
so.setProperty(property,newValue);
}

Changing the property value on the SharedObject will automatically trigger the SYNC event to all subscribers.

4. Working with server-side SharedObjects

Server-side SharedObjects are a part of the Red5 Pro Java API. With the server-side API, you can do everything that a client-side API can do and more.

Every Red5 Pro application class also known as a the application adapter, extends the MultiThreadedApplicationAdapter class. The MultiThreadedApplicationAdapter provides methods to work with SharedObjects.

Every SharedObject in Red5 Pro is referenced using the ISharedObject interface.

4.1 Creating a server-side SharedObject

The code below demonstrates how to create a ISharedObject at a requested scope and return it.

/**
 *
 * @param scope
 * @param name
 * @param persistent
 * @return
 * @throws Exception
 */
private ISharedObject initSharedObject(IScope scope, String name, boolean persistent) throws Exception{

    ISharedObject so = null;

    if(so == null)
    {
        if(so == null){
            this.createSharedObject(scope, name, persistent);
            so = this.getSharedObject(scope, name, persistent);
            if(so == null) throw new Exception("SharedObject was not created");
        }
    }

    if(!so.isAcquired()){
        so.acquire();
    }

    return so;
}

First we use the application adapter method getSharedObject) to find an existing SharedObject instance at the given scope & by the given name.

If a SharedObject is not found attempt to create a new one using the createSharedObject) method.

  • IScope scope : Indicates the resource location (scope) of the SharedObject in Red5 Pro.
  • String name : Name of the SharedObject
  • Boolean persistent : Whether the SharedObject is persistent or not

4.2 Acquiring the SharedObject

In the previous code snippet, notice that we make call to the method acquire on the SharedObject before returning it.

    if(!so.isAcquired()){
        so.acquire();
    }

Calling acquire on the SharedObject before use ensures that Red5 Pro will not dispose it automatically in any event unless explicitly released.

Every call to acquire on the SharedObject should be followed by a call to release subsequently.

4.3 Listening for SharedObject events

As we start using the acquired SharedObject, we may be interested to observe the SharedObject for updates and other important SharedObject events.

The Red5 Pro API provides us with the ISharedObjectListener interface which allows us to listen for crucial SharedObject events. To start observing an ISharedObject instance, we need to create a implementation of the ISharedObjectListener interface and add it to the SharedObject using the addSharedObjectListener method.

try
{
    ISharedObject so = initSharedObject(app, "mysharedobject", false);
    so.addSharedObjectListener(bucketListener);
}
catch (Exception e)
{
    e.printStackTrace();
}

/**
* Shared Object listener object for monitoring SharedObject events
*/
private ISharedObjectListener bucketListener  = new ISharedObjectListener(){

/*
 * (non-Javadoc)
 * @see org.red5.server.api.so.ISharedObjectListener#onSharedObjectConnect(org.red5.server.api.so.ISharedObjectBase)
 */
@Override
public void onSharedObjectConnect(ISharedObjectBase so) {
    log.info("Client connected to the SharedObject");
}

/*
 * (non-Javadoc)
 * @see org.red5.server.api.so.ISharedObjectListener#onSharedObjectDisconnect(org.red5.server.api.so.ISharedObjectBase)
 */
@Override
public void onSharedObjectDisconnect(ISharedObjectBase so) {
    log.info("Client disconnected from the SharedObject");
}

/*
 * (non-Javadoc)
 * @see org.red5.server.api.so.ISharedObjectListener#onSharedObjectUpdate(org.red5.server.api.so.ISharedObjectBase, java.lang.String, java.lang.Object)
 */
@Override
public void onSharedObjectUpdate(ISharedObjectBase so, String key, Object value) {
    log.info("SharedObject property {} is updated", key);
}

/*
 * (non-Javadoc)
 * @see org.red5.server.api.so.ISharedObjectListener#onSharedObjectUpdate(org.red5.server.api.so.ISharedObjectBase, org.red5.server.api.IAttributeStore)
 */
@Override
public void onSharedObjectUpdate(ISharedObjectBase so, IAttributeStore values) {
    log.info("SharedObject attribute store is updated");
}

/*
 * (non-Javadoc)
 * @see org.red5.server.api.so.ISharedObjectListener#onSharedObjectUpdate(org.red5.server.api.so.ISharedObjectBase, java.util.Map)
 */
@Override
public void onSharedObjectUpdate(ISharedObjectBase so, Map<String, Object> map) {
    log.info("SharedObject multiple properties are updated");
    for (Map.Entry<String, Object> entry : map.entrySet())
    {
        log.info(entry.getKey() + "/" + entry.getValue());
    }
}

/*
 *         (non-Javadoc)
 * @see org.red5.server.api.so.ISharedObjectListener#onSharedObjectDelete(org.red5.server.api.so.ISharedObjectBase, java.lang.String)
 */
@Override
public void onSharedObjectDelete(ISharedObjectBase so, String key) {
    log.info("Property {} deleted from SharedObject", key);
}

/*
 * (non-Javadoc)
 * @see org.red5.server.api.so.ISharedObjectListener#onSharedObjectClear(org.red5.server.api.so.ISharedObjectBase)
 */
@Override
public void onSharedObjectClear(ISharedObjectBase so) {
    log.info("SharedObject cleared");
}

/*
 * (non-Javadoc)
 * @see org.red5.server.api.so.ISharedObjectListener#onSharedObjectSend(org.red5.server.api.so.ISharedObjectBase, java.lang.String, java.util.List)
 */
@Override
public void onSharedObjectSend(ISharedObjectBase so, String method, List<?> params) {
    log.info("SharedObject send called for method {}", method);

}

};

The ISharedObjectListener provides us with useful information about connections to the SharedObject, disconnections from the SharedObject, property updates, property deletion etc: To know about each callback method check out the ISharedObjectListener javaDocs.

Check out the sharedobject-listener-demo sample application on github for more information on how to work with SharedObject listeners.

4.4 Writing to the SharedObject

At anytime if you wish to add / update the SharedObject, you need to get a reference to the SharedObject and then add/update one or more attributes on it.

Updating a single attribute value on the SharedObject from server side

/**
 * Triggers sync for each attribute write
 */
private void writeSharedObjectData(){

    ISharedObject so = this.getSharedObject(appScope, "mysharedobject");
    so.setAttribute("time", Instant.now().toEpochMilli());
}

Sometimes you may have multiple attributes to update. Using the previous code for doing multiple update will trigger multiple 'update' notifications to subscribers. To do it in one go we use the beginUpdate() and endUpdate() methods. In this case the SharedObject dispatch update notification only once (after it sees endUpdate()).

Updating multiple attributes on the SharedObject from server side

/**
 * Triggers sync only once for all writes
 */
private void writeSharedObjectData2(){

    ISharedObject so = this.getSharedObject(appScope, "mysharedobject");

    so.beginUpdate();
    so.setAttribute("time", Instant.now().toEpochMilli());
    so.removeAttribute("servername");
    so.setAttribute("capabilities", red5.CAPABILITIES);
    so.endUpdate();

}

Force an update notification

In selective use cases, you may want the SharedObject to send out an update notification even though there was no update made to the SharedObject. You can think of this as a refresh on the SharedObject data from the subscribing clients.

To do this we need to pass a boolean true to the SharedObject's setDirty method.

/**
 * Force an update notification dispatch
 */
private void forceDataSync(){

    ISharedObject so = this.getSharedObject(appScope, "mysharedobject");
    so.setDirty(true);
}

For more information on read/write operations on Red5 Pro SharedObjects, check out the sharedobject-readwrite-demo application on github

4.5 Reading from the SharedObject

To read a SharedObject you need to fetch a reference to the ISharedObject and reat the attributes stored on it, similar to how we stored the attributes previously.

/**
 * Reads attributes from SharedObject that we might have stored earlier
 */
private void readSharedObjectAttributes(){

    ISharedObject so = this.getSharedObject(appScope, "mysharedobject");

    if(so.hasAttribute("time")){
        log.info("Attr time  :  Value {}", so.getAttribute("time"));
    }

    if(so.hasAttribute("servername")){
        log.info("Attr servername  :  Value {}", so.getAttribute("servername"));
    }

    if(so.hasAttribute("capabilities")){
        log.info("Attr capabilities  :  Value {}", so.getAttribute("capabilities"));
    }
}

To read all attributes you can iterate over all attribute names one by one and read their values.

/**
 * Reads all attributes from SharedObject
 */
private void readAllSharedObjectAttributes(){
    ISharedObject so = this.getSharedObject(appScope, "mysharedobject");
    Set<String> attrs = so.getAttributeNames();

    for(String attr : attrs){
        log.info("Attr {}  : value {}", attr, so.getAttribute(attr));
    }
}

For more information on read/write operations on Red5 Pro SharedObjects, check out the sharedobject-readwrite-demo application on github

4.6 Clearing the SharedObject

When your SharedObject is stuffed with a lot of data and you want to clear it out without deleting the SharedObject, use the clear() method on the SharedObject.

This clears all the data from your SharedObject at once, which si faster than having to iterate over each attribute to delete it.

/**
 * Clears the ISharedObject of all its data
 */
private void clearharedObjectData(){
    ISharedObject so = this.getSharedObject(appScope, "mysharedobject");
    so.clear();
}

4.7 Closing the SharedObject

Finally when you are done with the SharedObject and with to terminate it from the server side use the close() method.

**
 * Destroys the SharedObject created
 *
 * @param scope
 * @param name
 * @param persistent
 */
private void destroySharedObject(){

    ISharedObject so = this.getSharedObject(appScope, "mysharedobject");

    if(so != null)
    {
        so.removeSharedObjectListener(soListener);
        so.close();
    }

    so = null;
}

For more information on read/write operations on Red5 Pro SharedObjects, check out the sharedobject-readwrite-demo application on github

5. Conclusion

SharedObjects are a great way of adding realtime interactivity to your application. With Red5 Pro, different types of clients (Flash, WebRTC & Mobile) can interact with each other and with the server in real-time.

Using Red5 Pro SharedObejcts You can create interactive real-time applications for education, chatrooms, IOT and much more. The possibilities are endless!