Thursday, July 23, 2009

Very Simple Remote Shared Object

Overview

In this example we'll see how you can use Flex's SharedObject class with Red5 to share data between connected clients. Data stored in an instance of the SharedObject class, when used remotely with a server, appears the same to all clients. When a client updates a property on a remote SharedObject instance, other clients see the same information. You can think of it like a replication object, a distributed cache, or a message bus. The data can be simple Strings or complex data structures. The server is capable of creating and updating SharedObjects as well. Updating from clients or servers both have their use in a variety of scenarios.

This example will show the most trivial case, where we want to sent simple Strings between connected clients.


Prerequisites

Client application

  1. In Eclipse, switch to the Flex Development Perspective.
  2. In the Flex Navigator view, right click and choose New -> Flex Project
  3. For a project name, use VerySimpleVideoPlayer, and then click Finish.
  4. In the project, open the file VerySimpleSharedObject.mxml
  5. Replace the default source with the following:


    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="connect()">
      
      <mx:Script>
        <![CDATA[
          /** A value of "success" means the client changed the shared object */
          private static const SUCCESS : String = "success";
          /** A value of "change" means another client changed the object or the server resynchronized the object. */
          private static const CHANGE : String = "change";
        
          
          private var _netConnection:NetConnection;
          private var _netStream:NetStream;
          private var _remoteSharedObject:SharedObject;
          
          /** We'll send randomly generated messages through the SharedObject to other clients. */
          [Bindableprivate var _nextMessage : String = int(Math.random()*100).toString();
        
          /** Called at creationComplete, this method will create the NetConnection to the server, 
           * required to establish the SharedObject.
           */
          public function connect() void {
            _netConnection = new NetConnection();
            _netConnection.objectEncoding = ObjectEncoding.AMF3
            _netConnection.client = this
        
            _netConnection.addEventListener(NetStatusEvent.NET_STATUS, onStatus);
            _netConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
            _netConnection.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            _netConnection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
        
              _netConnection.connect("rtmp://localhost/SimpleRed5Project");        
          }
          
          public function onStatusevent : NetStatusEventvoid {
            trace("NetConnection.onStatus " + event);
            if (event.info !== '' || event.info !== null) {  
              switch (event.info.code) {
                    case "NetConnection.Connect.Success":   createSharedObject();  break;
                    case "NetConnection.Connect.Closed":  trace("Disconnected");  break;
              }      
            }
          }
          
          private function createSharedObject() void {
            trace("Connected, creating SharedObject");
            _netStream = new NetStream(_netConnection);
            _netStream.addEventListener(NetStatusEvent.NET_STATUS, onStatus);
            _netStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
            _netStream.client = this;    
            
            _remoteSharedObject = SharedObject.getRemote("myDataObj", _netConnection.uri, false);
            _remoteSharedObject.client = this;
            _remoteSharedObject.addEventListener(SyncEvent.SYNC, onSyncEventHandler);
            _remoteSharedObject.connect(_netConnection);          
          }
          
          public function securityErrorHandler(event : SecurityErrorEventvoid {  trace('Security Error: '+event);  }
          public function ioErrorHandler(event : IOErrorEventvoid {  trace('IO Error: '+event);  }
          public function asyncErrorHandler(event : AsyncErrorEventvoid {  trace('Async Error: '+event);  }  


          private function onSyncEventHandler(event : SyncEvent):void {
            trace("onSync code: " + event.changeList[0].code);  
            switch(event.changeList[0].code) {
              case SUCCESS:  processUpdate(event.target.data["message"], event.target.data["clientID"]);    break;
              case CHANGE:  processUpdate(event.target.data["message"], event.target.data["clientID"]);    break;
            }    
          }

           private function processUpdatepMessage : String, pClient : String void {
            _messageLabel.text = pMessage;
            _clientLabel.text = pClient;
          }
     
          private function sendMessage() void {
            _remoteSharedObject.setProperty("clientID", _clientID.text );
            _remoteSharedObject.setProperty("message", _nextMessageLabel.text;
            // Create a new random message for next time.
            _nextMessageLabel.text = int(Math.random()*100).toString();
          }
          
        ]]>
      </mx:Script>
      
      <mx:Label text="Message"  x="437" y="60"/>
      <mx:Label id="_messageLabel"  x="514" y="60"/>
      <mx:Label text="Updated by"  x="437" y="90"/>
      <mx:Label id="_clientLabel"  x="514" y="90"/>
      
      <mx:Label text="Client ID (randomly generated)" x="10" y="60"/>
      <mx:TextInput text="{int(Math.random()*100)}" id="_clientID"  x="199" y="58"/>
      
      <mx:Label text="Next message"  x="106" y="90"/>
      <mx:TextInput text="{_nextMessage}" id="_nextMessageLabel"  x="199" y="88"/>
      <mx:Button label="Send a message" click="sendMessage()"  x="199" y="118"/>
      
    </mx:Application>






Testing your application

  1. Launch your client application by right clicking on the project and choosing Debug As -> Flex Application
  2. Your browser should launch with the Flex application loaded.
  3. Copy the location from the browser bar.
  4. Launch a new browser window
  5. Paste the location you copied in step #3 into the address bar and load the URL. This should load the Flex application a second time.
  6. Arrange the browser windows so you can see both Flex application instances at the same time. It should preferably look like this:

    Note that each window should have a different Client ID. If you get the same numbers by random luck, you can change them manually or relaunch the application. In a non-example application, you wouldn't use random numbers on the client to generate IDs.
  7. With both windows visbible, click the "Send a message" button in one of your two browser windows. You should see the "Message" and "Updated by" fields update in both windows:


  8. Click the "Send a message" button in the other browser window. Again both "Message" and "Updated" fields should be updated, this time with the client ID of the second window. When a SharedObject is updated, a SyncEvent is processed by all clients, including the client that updated the SharedObject.


Further Reading

Wednesday, July 8, 2009

What Streaming Protocols Do The Major CDNs Support?

As a followup to the previous article on which streaming protocols are supported by the major media servers, here is a list of which protocols are supported by the major CDNs. These are the premier partners implementing the Flash Video Streaming Service or FVSS, and AT&T for comparison. The RTMP protocol and the variants listed below are what enable streaming, which enjoy several advantages and disadvantages versus basic progressive download over HTTP. Akamai and Limelight have long been involved with Flash, and Akamai engineers have spoken at Adobe Max events in the past.

Keep in mind that there are many factors to consider when choosing a CDN. For example, there might be Flash license fees if you partner with a CDN beyond the cost of basic service. Relative newcomer Level3 reportedly has a pricing advantage, although every individual quote and contract may vary, so do your own research. It's also important to note that the CDNs, through the FVSS, support downstream content only. They do not support recording of content from clients.




Further Reading


Check out this very useful comparison of server and service options to help understand what you get and how much it might cost.

Thursday, July 2, 2009

Very Simple Video Player


Overview


The Flex Framework and Red5 make it very easy to make a video player system with trivial client and server application code. In fact, the code to do so can easily fit on a single page. In this article we'll explore just how to do so with Flex 3 and Red5 0.8.


Prerequisites


Notes

It's important to note that this example oversimplifies things; in real world scenarios we'll likely want to use something a little (or a lot) more complex for enhanced functionality.

Its recommended that you follow the instructions below and create your own projects. However, if you require it, the client application code is available via anonymous SVN from google code from the repository location http://tcp1935.googlecode.com/svn/ in the examples/VerySimpleVideoPlayer subdirectory.

Server Application

  1. Follow the directions in the article Developing Your First Red5 Application to generate a Red5 server application. The resulting server will actually be a completely functional server application, so you're already done with all the code that is needed on the server.
  2. We do need to configure our server with a sample video to play. To provide you with a sample, I pulled the mp4 version of the TBBS episode of a collection called BBS Documentaries. I then used a flv and mp4 enabled build of ffmpeg to reencode it as flv video:
    ffmpeg -t 60 -i 1993-bbs-tbbstape_512kb.mp4 -ar 22050 test-bbs.flv

    The video is available via anonymous svn from google code here:
    http://code.google.com/p/tcp1935/source/browse/#svn/examples/assets/video

    The video is shared under a Creative Commons license from archive.org, so consider the sample under the same license.

  3. We have to make this video available to our Red5 server application. To do so, go to your server project (SimpleRed5Project, if you are following the previous example) in Eclipse. In the WebContent folder, create a new directory named streams. It is from this default location that the Red5 ApplicationAdapter will search for content requested by client connections.
  4. Copy and paste the test-bbs.flv file into the streams directory.
  5. Start your Red5 Server Runtime, which should be configured to run your Red5 server application. Your server application should then automatically republish and include the flv file in the deployment.
  6. That's it! You should now have a functioning sample server application that can stream a video, having written no code of your own and just doing some basic configuration.

Client Application

  1. In Eclipse, switch to the Flex Development Perspective.
  2. In the Flex Navigator view, right click and choose New -> Flex Project
  3. For a project name, use VerySimpleVideoPlayer, and then click Finish.
  4. In the project, open the file VerySimpleVideoPlayer.mxml
  5. Replace the default source with the following:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:VideoDisplay source="rtmp://localhost/SimpleRed5Project/test-bbs.flv" width="320" height="240" id="_video" />
    <mx:Button label="Play" click="_video.play()" />
    <mx:Button label="Stop" click="_video.stop()" />
    </mx:Application>


  6. That's all there is to it. Save the file, and we're ready to give it a test.


Testing Your Work

  1. Launch your client application by right clicking on the project and choosing Debug As -> Flex Application.
  2. Your browser should launch and show the video application we just developed. After clicking the play button, the video should beginning playing after a very slight delay. The output should then look like this:
  3. If you see the video playback correctly, then everything worked perfectly. Great! You've just created a (very) simple video playback system with your own Red5 streaming media server on one side and a Flex client on the other.

Overview of the Flex Communication APIs

One of the reasons for starting this blog was to further the available documentation for the Red5 project. Oddly enough, however, even when you ignore the specific server technology you use, there aren't many good overviews of the APIs available to you as a Flex developer. Below is an overview of the available client-side APIs for various server communication functions. This is only meant as a quick reference and not as an instructive guide. To learn each API you will need to review examples and use them in your own programs. Note that depending on which server platform you choose to use, only a subset of these APIs will implemented.

Streaming

NetConnection
A NetConnection is a pipe between a client and a server. It is used for two purposes:
  1. Constructing NetStream objects (see NetStream) and acting as the pipe over which those NetStreams flow.
  2. Calling methods on the server side application class handling the connection via the call() method.
NetStream
A NetStream is a one-way channel created over a NetConnection for the sending or receiving of streaming
data. Streams are sent with publish() and received with play(). The data is usually going to be audio or video, but it is also possible to send objects as well using the send() method. Audio and video streams can be either live or prerecorded (often described as video-on-demand or VOD).

Depending on the usage, the connection over which the NetStream is made can be to a server, a
peer, or the local file system.

RPC

HTTPService
Makes a simple aynchronous HTTP or HTTPS call using the Responder pattern.

WebService
Makes a call to a SOAP web service. Like HTTPService, uses HTTP or HTTPS as the transport.

RemoteObject

A Remote Object is a client-side stub that gives you access to the matching methods on a server-side class as if it were local to the client.

Messaging

Consumer
Used to receive asynchronous messages. Messages can be generated from the server or another client, but must be routed through the server.

Producer
The Producer class sends aynchronous messages. These messages are sent to the server, which can process them and optionally deliver them to other clients.

SharedObject
When used in a remote manner, SharedObject acts sort of like a Producer and Consumer rolled up in one. The SharedObject component does just what you might expect from its name: it maintains a piece of data on a server that appears the same to all connected clients.

DataManagement

DataService
Part of the Data Management Service of LCDS. The DataService API allows you to easily retrieve data from a remote database-connected service.

Low-Level Protocols

Socket
Creates a TCP socket. This isn't usually listed as a communication API in Adobe documentation, probably because there are no default implementations on the server side that don't speak a higher level protocol such as HTTP.

XMLSocket

Speaks a simple custom protocol for exchanging XML messages between a client and server

Further Reading

In writing this article I came across an excellent post that covers getting started with LCDS and gives a quick overview of the APIs available on that platform, as well as references to other places to pick up information.