msgbartop
Various ramblings-on, mostly about Red5
msgbarbottom

06 Jun 09 PHP support in Red5

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: , , , , ,

05 Jun 09 Red5 0.8.0 released

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.

Tags: , ,

03 Jun 09 Multiple HTTP socket configurations

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:

  • http://192.168.0.1:8080/
  • http://192.168.0.2:5080/
  • http://192.168.0.2:8080/
  • http://10.0.0.2:5080/
  • http://10.0.0.2:8080/

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: , , , ,

25 May 09 Memory leak?

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: , , , , ,

08 May 09 Flash on Tap: Red5 Workshop

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: , , , , ,

05 May 09 Support for other SLF4J loggers

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: , , , , ,

15 Apr 09 Java book recommendations

I see this question quite a lot, “What books should I read to learn Java”. I have a set of books I like to recommend, books that have made me a better developer. So without further adieu..



Effective Java


Java concurrency in practice

For Red5 development I also recommend:



Spring in Action


Professional Apache Tomcat 6

Lastly, you can usually pick these books up even cheaper if you click on the “used” section on Amazon.

Heres a snapshot of two of my shelves of books.

Tags: , , , , , ,

07 Apr 09 On-demand room / scope creation

I am not sure how or if FMS provides a means for dynamic rooms, but Red5 does and I will try to explain how to use it below. For the following example, you should expect the following url – rtmp://localhost/myapp/room is being used. The method below is an overridden super class method from the Red5 application adapter. The example places a client into an id based “chat” room; the id algorithm could be anything you desire and is not fixed. I have used this block in a couple projects, one of which used an encrypted room and id portion and its url looked similar to this – rtmp://localhost/myapp/29c8109abd29e

	@Override
	public boolean roomJoin(IClient client, IScope scope) {
		log.debug("roomJoin - client id: {} scope: {}", client.getId(), scope);
		
		IConnection conn = Red5.getConnectionLocal();

		String scopeName = scope.getName();
		log.debug("Scope name: {}", scopeName);
		
		//check for "application" scope
		if ("myapp".equals(scopeName)) {
			log.debug("Connection already connected to app scope");
		} else if (conn.hasAttribute("in.room")) {
			//client is already in a room
			log.debug("Connection already connected to room scope");
		} else {
			//the room name
			String roomName = scopeName;						
			//set flag
			conn.setAttribute("in.room", true);
			//get app scope
			IScope appScope = scope.getParent();
			//handle at room level
			if ("room".equals(roomName)) {
				//lookup room (top level)
				IScope roomScope = ScopeUtils.resolveScope(appScope, roomName);
				if (roomScope == null) {
					if (appScope.createChildScope(roomName)) {
						log.debug("Room {} created", roomName);
						roomScope = appScope.getScope(roomName);
					} else {
						log.warn("Room {} was not created", roomName);
					}
				} else {
					log.debug("Room scope {} was found", roomName);
				}				
				//get the next room identifier
				String chatId = getNextChatId();
				//room for chat id
				IScope chatScope = ScopeUtils.resolveScope(roomScope, chatId);
				if (chatScope == null) {
					if (roomScope.createChildScope(chatId)) {
						log.debug("Chat scope {} created", chatId);
						chatScope = roomScope.getScope(chatId);
					} else {
						log.warn("Chat scope {} was not created", chatId);
					}
				} else {
					log.debug("Chat scope {} was found", chatId);
				}				
				   								
				//send the actual join
				return roomJoin(client, chatScope);
			} else {
				return false;
			}
		}
		
		return super.roomJoin(client, scope);
	}

After this block is executed, your client would be connected to something like this – rtmp://localhost/myapp/room/1001 without having to create any additional NetConnections. The method “getNextChatId()” is meant to provide a chat room id, based on whatever criteria you would like.

Tags: , , , , , ,

23 Mar 09 Spring lifecycle and Red5

I just fixed a huge bug in Red5 (r3574) and I am shocked that no one noticed it (the bug) before I did. The problem occured when the server was shutdown via JVM exit or our shutdown scripts. The Spring destroy and DisposableBean hooks were not being executed as would be expected. I’m sure there were at least a few people who thought their apps were cleaning up properly via their “destroy-method” bean attributes, like I did. Below you will find my test application, should you want to test it yourself. Enjoy… 

Example project: r5_shutdown.zip

Tags: , , , ,

19 Feb 09 Xuggler final

Having been in the media streaming game for quite some time, esentially since its birth on the internet. I have needed conversion and transcoding tools countless times. These tools usually needed to interoperate with Java in one way or another. I have used the various offerings throughout the years, from JMF, FMJ, QuickTime, and helloNetwork stuff to accomplish my tasks. None of these tools worked as seemlessly nor flawlessly as Xuggler does. One of the best things about it, is that it works with Red5 for live transcoding! 

Another key point to consider is that it works everywhere (osx, linux, and win); this is an important feature when I consider tools. So are you ready to buy a copy now? Guess what, it gets better.. Xuggler is FREE and it can be built compliant to GPL or LGPL!

Kudos go to Art and Robert, you’ve created an awesome open source tool and I am glad to use it.

http://blog.xuggle.com/

Lastly, if youre attending FITC in Amsterdam next week you should stop and see Arts demo.

Tags: , , , , , , , ,


Fatal error: Call to undefined function akismet_counter() in C:\xampp\htdocs\paulgregoireblog\wp-content\themes\googlechrome\footer.php on line 9