When you want to schedule a particular task or job within Red5, you have at least two options available which are built into the server. We use Quartz for our scheduler, but you are not required to use it; I just wanted to get that disclaimer out of the way, this is Java so use whatever you like. The first option is the “server-wide” scheduler which is created when the server starts up and requires no configuration from your application. This scheduler is sufficient for most of the jobs you may want to create, but you will run into problems if you attempt to access an application class from within your job. The second option resolves this issue by providing an application level scheduler, one which can access your classes. The only caveat with this scheduler is that you must provide some configuration details, your applications context name.
Accessing the “server-wide” scheduler is done in your application like so (using scope):
schedulingService = (ISchedulingService) scope.getContext().getBean(ISchedulingService.BEAN_NAME);
Accessing the application scheduler is slightly different (using app context):
schedulingService = (ApplicationSchedulingService) ((BeanFactory) applicationContext).getBean("scheduler");
I prefer to make sure the schedulingService member is setup in my “appStart” method, since this is essentially where the application is “started” within Red5.
The following entry must be made in your applications red5-web.xml file if you want to use an application scheduler:
<bean id="scheduler" class="org.red5.server.scheduling.ApplicationSchedulingService">
<property name="applicationName" value="myapp" />
</bean>
Make sure you set the correct application name / context (replace “myapp”).
Once you have the scheduling service, you can add your job amongst many other options which I don’t want to cover here.
Tags: Java, job, quartz, Red5, schedule, task, timer
I’ve updated my “parameterdemo” to include a couple PHP pages. The latest trunk (0.8.1-dev) now supports PHP through the use of Quercus. To enable it simply set a context parameter in your web.xml and include the quercus and resin-utils jars in your WEB-INF/lib. See the zip file for a complete example (eclipse + ant).
Once you deploy the war, go to these urls to test (update your port to match your servers setting):
PHP example getting value from Red5 application:
<a href="http://localhost/parameterdemo/getparam.php">http://localhost/parameterdemo/getparam.php</a>
Same example with debug output:
<a href="http://localhost/parameterdemo/getparam.php?debug=true">http://localhost/parameterdemo/getparam.php?debug=true</a>
Java servlet example:
<a href="http://localhost/parameterdemo/myservlet">http://localhost/parameterdemo/myservlet</a>
The web.xml entry must look like so:
<context-param> <param-name>enable-php</param-name> <param-value>true</param-value> </context-param>
Note: You dont have to install Resin to use Quercus, simply download one of the wars on this page: http://quercus.caucho.com/ and grab the libraries from WEB-INF/lib
Tags: bean, Java, parameter, PHP, Red5, servlet
Robert and Art continue to amaze me with their Xuggler project. If you are like me and have had to use FFMPEG in a project (Java in my case), you know how hard it can be to get everything working correctly. Xuggler has some how made this even more easy than with their 1.0 version. Through the use of their MediaTool you can do some incredible stuff; the stuff that should have been possible with JMF! Do yourself a favor and check out their post about the MediaTool.
As a minor side note, Xuggler has changed its license to AGPL with the 3.0 release.
Tags: ffmpeg, Java, jmf, media, xuggler
Come one, come all.. Red5 0.8.0 is now available. Make your way to our google code page and download the installer for your platform. http://code.google.com/p/red5/
PS. If someone wants to build the Debian package, we are looking for a volunteer.
Red5 version 0.8.0 introduces the ability to bind multiple ports and hosts for HTTP access; starting at revision 3632. Previously there were other options to accomplish this feature, but now it is built-in. So I’ll get right to it. The older configuration style for the tomcat server bean was like so:
<bean id="tomcat.server" class="org.red5.server.tomcat.TomcatLoader" init-method="init" destroy-method="shutdown" depends-on="context.loader">
<property name="webappFolder" value="${red5.root}/webapps" />
<property name="connector">
<bean class="org.apache.catalina.connector.Connector">
<constructor -arg type="java.lang.String" value="org.apache.coyote.http11.Http11NioProtocol" />
<property name="port"><value>${http.port}</value></property>
<property name="redirectPort"><value>${https.port}</value></property>
<property name="enableLookups"><value>false</value></property>
</bean>
</property>
<property name="baseHost">
<bean class="org.apache.catalina.core.StandardHost">
<property name="name" value="${http.host}" />
<property name="unpackWARs" value="true" />
<property name="autoDeploy" value="true" />
<property name="xmlValidation" value="false" />
<property name="xmlNamespaceAware" value="false" />
</bean>
</property>
</bean>
This allowed for one connector (port) and one host (like www.red5.org). If http.host were set to “192.168.0.1″ and http.port was set to “5080″, then you would be able to connect to “http://192.168.0.1:5080/” in your browser.
This latest configuration still supports this configuration, but allows for much more.
<bean id="tomcat.server" class="org.red5.server.tomcat.TomcatLoader" init-method="init" destroy-method="shutdown" depends-on="context.loader">
<property name="webappFolder" value="${red5.root}/webapps" />
<property name="connectors">
<list>
<bean id="defaultHttp" class="org.apache.catalina.connector.Connector">
<constructor -arg type="java.lang.String" value="org.apache.coyote.http11.Http11NioProtocol" />
<property name="port"><value>${http.host}</value></property>
<property name="redirectPort"><value>8080</value></property>
<property name="enableLookups"><value>false</value></property>
</bean>
<bean id="defaultProxy" class="org.apache.catalina.connector.Connector">
<constructor -arg type="java.lang.String" value="org.apache.coyote.http11.Http11NioProtocol" />
<property name="port"><value>8080</value></property>
<property name="redirectPort"><value>${https.port}</value></property>
<property name="enableLookups"><value>false</value></property>
</bean>
</list>
</property>
<property name="baseHost">
<bean class="org.apache.catalina.core.StandardHost">
<property name="name" value="${http.host}" />
<property name="unpackWARs" value="true" />
<property name="autoDeploy" value="true" />
<property name="xmlValidation" value="false" />
<property name="xmlNamespaceAware" value="false" />
</bean>
</property>
<property name="hosts">
<list>
<bean id="local2" class="org.apache.catalina.core.StandardHost">
<property name="name" value="192.168.0.2" />
<property name="autoDeploy" value="false" />
<property name="xmlValidation" value="false" />
<property name="xmlNamespaceAware" value="false" />
</bean>
<bean id="local3" class="org.apache.catalina.core.StandardHost">
<property name="name" value="10.0.0.2" />
<property name="autoDeploy" value="false" />
<property name="xmlValidation" value="false" />
<property name="xmlNamespaceAware" value="false" />
</bean>
</list>
</property>
</bean>
This allows for two connector (ports on each host) and three hosts. If http.host were set to “192.168.0.1″ and http.port was set to “5080″, then you would be able to connect to “http://192.168.0.1:5080/” in your browser. In addition you would also have these urls available:
Each application (context) within your red5/webapps directory is made available on each host as well.
Lastly, the “host” name does not have to be an IP address; you can specify a valid dns host name instead.
Tags: http, port, Red5, socket, tomcat
A poster to the Red5 list posted this today and claims that it exposes a memory leak in the server. The individual states that he doesnt know Java very well, so having been in the Java-game for 10+ years I immediately spot a potential leak in his method.
public void privMessage(String sendto, String message) {
IConnection conn = Red5.getConnectionLocal();
IClient client = conn.getClient();
IScope scope = getScope();
IContext context = getContext();
IClientRegistry reg = context.getClientRegistry();
if (reg.hasClient(sendto)) {
IClient recip = reg.lookupClient(sendto);
Set[IConnection] rcons = recip.getConnections(scope);
Iterator[IConnection] it = rcons.iterator();
while (it.hasNext()) {
IConnection rcon = it.next();
if (rcon instanceof IServiceCapableConnection)
((IServiceCapableConnection) rcon).invoke("privComm", new Object[]{client.getId(),message});
}
}
}
The big red flag for me is the use of an Iterator and the second thing is the use of an object reference within a loop. So to fix these potential leak candidates, I offer two solutions below.
Removal of the Iterator by using for-each:
IConnection conn = Red5.getConnectionLocal();
IClient client = conn.getClient();
IContext context = getContext();
IClientRegistry reg = context.getClientRegistry();
if (reg.hasClient(sendto)) {
IClient recip = reg.lookupClient(sendto);
String clientId = client.getId();
Set[IConnection] rcons = recip.getConnections(scope);
for (IConnection rcon : rcons) {
if (rcon instanceof IServiceCapableConnection)
((IServiceCapableConnection) rcon).invoke("privComm", new Object[]{clientId,message});
break;
}
}
Overall I think this is the best solution and is what I would implement:
1. In your ApplicationAdapter, create a map containing your client id to connection mappings
// map to keep track of connections by client id private static ConcurrentMap[String, IConnection] connectionMap = new ConcurrentHashMap[String, IConnection](31);
2. In your connect method, add the incomming clients to the map
@Override
public boolean connect(final IConnection conn, IScope scope, Object[] params) {
// call original method of parent class
if (!super.connect(conn, scope, params)) {
return false;
}
// get the connections client id
IClient client = conn.getClient();
if (client != null) {
String clientId = client.getId();
// check the map for the client
if (!connectionMap.containsKey(clientId)) {
// register the connection
connectionMap.put(clientId, conn);
} else {
log.warn("Client id {} already exists in connection map",
clientId);
}
}
return true;
}
3. Simplify your method
public void privMessage(String sendto, String message) {
IConnection conn = Red5.getConnectionLocal();
IClient client = conn.getClient();
//senders client id
String clientId = client.getId();
if (connectionMap.containsKey(sendto)) {
IConnection rcon = connectionMap.get(sendto);
if (!ServiceUtils.invokeOnConnection(rcon, "privComm", new Object[]{clientId, message})) {
log.warn("Private message to client id: {} failed", sendto);
}
} else {
//notify sender that recipient is not available
ServiceUtils.invokeOnConnection(conn, "onError", new Object[]{"Client was not available"});
}
}
4. Dont forget to remove the map entries when the client disconnects
@Override
public void appDisconnect(IConnection conn) {
// get the previously stored id
String clientId = conn.getClient().getId();
// unregister user
IConnection cn = connectionMap.remove(clientId);
}
Please note that for the generic type args, I am using square braces for word press formatting reasons. Make sure you replace them with < and > before using the code
Tags: concurrent, for each, Iterator, Java, leak, Red5
FOT is coming up real soon… I’m co-presenting with Dominick for the Red5 workshop and I would like to know what you guys want to see? Since its an all-day thing, I would be glad to get some input from the community. Post your ideas as comments.
Tags: flash, flash on tap, flex, fot, Java, Red5
I wrote a new Android application today while on my “Vacation”, this one is based on one of my favorite classic games – Asteroids. I still have a lot of bugs and clean up to complete, but by looking at the screencast you can see that it is playable.
Tags: Android, asteroids, game, Java
Quite some time ago, I wrote an application for the Danger Hiptop that displayed falling text like in the Matrix. I have decided to port this to Android as my first “learning” application. Here is a screencast of my first “working” version. I also want to point out that I wasn’t able to port the application but instead have rewritten it from scratch; which is probably better in the long-run.
After having some other things to do etc.. heres an update after 2 or so hours
Ok, so I went and had dinner with the family and then worked on it a bit more, without further adieu –
Best and latest build
I have also packaged it up as an unsigned apk, but you cannot load this unless your phone has Cupcake (1.5); which for me and the rest of the US is sometime next week.
I recently updated Red5 trunk to fix APPSERVER-369 and this allows the use of Log4j or any other SLF4j implementation. There may be a few quirks to workout but here is the procedure for using Log4j instead of the default implementation of Logback.
1. Edit all of your application web.xml files and remove any logging context listeners or filters and replace them with this:
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
2. Edit your red5.bat or sh to include the slf4j adapter jar and the latest log4j jars:
set RED5_CLASSPATH=%RED5_HOME%\boot.jar;%RED5_HOME%\lib\slf4j-log4j12-1.5.6.jar;%RED5_HOME%\lib\log4j-1.2.15.jar;%RED5_HOME%\conf;%CLASSPATH%
3. While in the startup script (red5.bat/sh) you must remove this entry from the LOGGING_OPTS:
-Dlogback.ContextSelector=org.red5.logging.LoggingContextSelector
4. Remove the following jars from your red5/lib directory:
logback-core-*.jar logback-classic-*.jar log4j-over-slf4j-1.5.6.jar
5. Add these jars to the red5/lib directory:
log4j-1.2.15.jar slf4j-log4j12-1.5.6.jar
6. Put your’e log4j configuration files in the red5/conf directory
7. Restart your server
Now when I did my test, I was not able to get the individual application logs so I’m not sure what is missing… maybe one of you know?
Tags: Java, log4j, logback, logging, Red5, slf4j