msgbartop
Various ramblings-on, mostly about Red5
msgbarbottom

06 Jun 09 FFMPEG made super simple

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

Buzz it!

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

Buzz it!

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

Buzz it!

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

Buzz it!

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

Buzz it!

07 May 09 Azdroids

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

Buzz it!

06 May 09 My first Android application

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. :(

Tags: , ,

Buzz it!

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

Buzz it!

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

Buzz it!

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

Buzz it!
10,986 spam comments
blocked by
Akismet