Red5 Video Chat Example

Alright so I’ve finally got my Red5 server up, running and working with a simple video chat. It took some looking around, mostly because there aren’t a lot of comprehensive tutorials out there and most of the ones I did find were outdated, so hopefully this will make it easier for beginners to get up and running.

By the way, if you want to see my final product actually working, go to http://Zazzer.me! It actually doesn’t use the Red5 component (I decided Cirrus and peer to peer was much better for my purposes) but it covers part of this.

Debugging the server-side code, however, has still has been a bit of a mystery to me. I’ve been running the standalone Red5 server that you can find here: http://red5.org/wiki/0_9_1 (that’s the latest release at the time of this post, 9.1). From the searching I’ve done until now the only answer I got was to run the war from inside tomcat and debug it that way. I’ll probably resort to that once I run across a problem that logging doesn’t fix, but that hasn’t happened yet so I may follow this up later.

If you’re not using Eclipse, this won’t be quite as useful to you. I’m using the Red5 plugin which builds the project and deploys it to the Red5 server for you. Getting it working involves creatively following: http://red5.org/wiki/Red5Plugin and then, http://red5.org/wiki/Red5Plugin/CreatingRed5Projects.

Those two together should get you up and running with a new dynamic web project. For the sake of this tutorial I’m going to assume you called yours Chatter. From there you need to edit your web.xml file. Mine looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
       <display-name>Chatter</display-name>
 
	<context-param>
		<param-name>webAppRootKey</param-name>
		<param-value>/Chatter</param-value>
	</context-param>
 
    <context-param>
	    <param-name>globalScope</param-name>
	    <param-value>default</param-value>
	</context-param>
 
    <context-param>
	    <param-name>contextConfigLocation</param-name>
	    <param-value>/WEB-INF/red5-*.xml</param-value>
	</context-param>
 
	<context-param>
		<param-name>locatorFactorySelector</param-name>
		<param-value>red5.xml</param-value>
	</context-param>
 
	<context-param>
		<param-name>parentContextKey</param-name>
		<param-value>default.context</param-value>
	</context-param>
 
	<servlet>
		<servlet-name>rtmpt</servlet-name>
		<servlet-class>org.red5.server.net.rtmpt.RTMPTServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
 
	<servlet-mapping>
		<servlet-name>rtmpt</servlet-name>
		<url-pattern>/fcs/*</url-pattern>
	</servlet-mapping>
 
	<servlet-mapping>
		<servlet-name>rtmpt</servlet-name>
		<url-pattern>/open/*</url-pattern>
	</servlet-mapping>
 
	<servlet-mapping>
		<servlet-name>rtmpt</servlet-name>
		<url-pattern>/close/*</url-pattern>
	</servlet-mapping>
 
	<servlet-mapping>
		<servlet-name>rtmpt</servlet-name>
		<url-pattern>/send/*</url-pattern>
	</servlet-mapping>
 
	<servlet-mapping>
		<servlet-name>rtmpt</servlet-name>
		<url-pattern>/idle/*</url-pattern>
	</servlet-mapping>

That will tell Red5 where it’s configuration files are. They should be placed along side it (in the WebContent/WEB-INF/ folder). The first, red5-web.properties should include:

1
2
webapp.contextPath=/Chatter
webapp.virtualHosts=*, localhost, localhost:8088, 127.0.0.1:8088

Next red5-web.xml should include:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
 
	<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	    <property name="location" value="/WEB-INF/red5-web.properties" />
	</bean>
 
	<bean id="web.context" class="org.red5.server.Context" 
		autowire="byType" />
 
	<bean id="web.scope" class="org.red5.server.WebScope"
		 init-method="register">
		<property name="server" ref="red5.server" />
		<property name="parent" ref="global.scope" />
		<property name="context" ref="web.context" />
		<property name="handler" ref="web.handler" />
		<property name="contextPath" value="${webapp.contextPath}" />
		<property name="virtualHosts" value="${webapp.virtualHosts}" />
	</bean>
 
	<bean id="web.handler" 
	    class="chatter.Application" 
		singleton="true" />
 
</beans>

The most important feature here is the “web.handler” bean. It directs red5 to where you keep your Java class that people connect to. In this case I wrote chatter.Application. Chatter is the package, Application is the name of the class. It extends ApplicationAdapter and is called Application by convention.

Here is an example Application file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
 
package chatter;
import java.util.HashMap;
 
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.service.*;
import static java.lang.System.*;
import java.util.Stack;
 
public class Application extends ApplicationAdapter{
	//private static final Log log = LogFactory.getLog( Application.class );
 
	public HashMap<String,User> users = new HashMap<String,User>();
	public Stack<String> vidStreams = new Stack();
 
	public boolean appStart(IScope scope){
		out.println("Setting up"+vidStreams.size());
		vidStreams.push("left");
		vidStreams.push("right");
		return true;
	}
 
	public void appStop(){
	    // This function fires when the app is closing
	}
 
	public double addSomething(double a, double b){
            // This is a method we will call from our flash client
		out.println("Adding: "+a+" + "+b);
		return a+b;
	}
 
	public String streamer(Object name){
            // Here is another example of a method we'll call from the flash client
            // I'm using System.outs instead of logging because I've had some trouble
            // with the logging.  I'll update it if I get it working.
 
		out.println("StreamSelect parameter: "+ name.toString());
		IConnection current = Red5.getConnectionLocal();
		String uid = current.getClient().getId();
		out.println("current: "+current.getClient().toString());
		User user = users.get(uid);
		out.println("username: "+user.username);
		out.println("uid: "+uid);
		out.println("stream: "+user.stream);
		return user.stream;
	}
 
	public boolean connect(IConnection conn, IScope scope, Object[] params) {
            // This is the master connection method called every time someone connects
            // to the server.
 
		out.println("Number of parameters passed: "+ params.length+" Value: "+params[0]);
		out.println("vidStreams peek: "+vidStreams.peek());
 
		// Check if the user passed valid parameters.
		//params = pseudo onlineStatus role sexe  room world
 
 
		if (params == null || params.length == 0) {
			out.println("No Username Passed");
			//Reject client terminates the execution of current client
			rejectClient("Username required, client rejected.");
		}
 
		if (vidStreams.empty()){
			out.println("Quota full, rejected");
			rejectClient("Quota full, rejected");
		}
 
		//ID increments each connection
		String uid = conn.getClient().getId();
		String username = params[0].toString();		
		String stream = vidStreams.pop();
 
		out.println("User setup, username:"+username+" stream: "+stream);
		// Call original method of parent class.
		if (!super.connect(conn, scope, params)) {
			return false;
		}	
		out.println("1");
		User user = new User(uid,username,stream);
		users.put(uid, user);
 
		ServiceUtils.invokeOnAllConnections(scope, "joinuser", new Object[] {username,uid} );		
		return true;
	}
	/*
	 * (non-Javadoc)
	 * @see org.red5.server.adapter.ApplicationAdapter#disconnect(org.red5.server.api.IConnection, org.red5.server.api.IScope)
	 * disconnect an user form the chat and notify all others users 
	 */
	public void disconnect(IConnection conn, IScope scope) {
            // Function called every time someone disconnects from the server.
 
		// Get the previously stored username.
		String uid = conn.getClient().getId();	
		//out.println("Disconnection: "+uid);
		// Unregister user.
		String stream = users.get(uid).stream;
		String username = users.get(uid).username;
		users.remove(uid);
 
		vidStreams.push(stream);
 
		out.println("Removed: "+username+" with: "+stream+" and uid:"+uid);
 
		ServiceUtils.invokeOnAllConnections(scope, "removeuser", new Object[] {username,uid} );
		super.disconnect(conn, scope);
 
	}	
 
	public boolean joinuser(String username, String uid) {
		out.println("join invoked: "+username+" "+uid);
		return true;
	}
 
	public boolean removeuser(String username, String uid) {
		out.println("remove invoked: "+username+" "+uid);
		return true;
	}
 
}

I also have a separate class for Users:

1
2
3
4
5
6
7
8
9
10
11
12
13
package chatter;
 
public class User {
	public String uid = null;
	public String username = null;
	public String stream = null;
 
	public User(String uid,String username,String stream) {
	      this.uid = uid;
	      this.username = username;
	      this.stream = stream;		      
	}
}

Now that all of the server stuff is ready, create a new .fla file and in the properties inspector for the stage, go to Class and write whatever you want there. Since we’ve already named everything else Chatter we’ll stick with that. Flash will use the name of that class to look for .as files associated with it. Create a new file named “Chatter.as” (remember this name has to match whatever you named the class in Flash), and then put this actionscript in it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package
{
  import flash.display.Sprite;
  import flash.display.MovieClip;
  import flash.events.NetStatusEvent;
  import flash.net.NetConnection;
  import flash.net.NetStream;
  import flash.media.Camera;
  import flash.media.Microphone;
  import flash.media.Video;
  import flash.net.Responder;
 
  public class Chatter extends Sprite
  {
        private var nc:NetConnection;
        private var good:Boolean;
        private var netOut:NetStream;
        private var netIn:NetStream;
        private var cam:Camera;
        private var mic:Microphone;
        private var responder:Responder;
	private var r:Responder;
        private var vidOut:Video;
        private var vidIn:Video;
        private var outStream:String;
        private var inStream:String;
 
        public function Chatter()
        {
             var rtmpNow:String="rtmp://localhost/Chatter";
             nc=new NetConnection;
             nc.connect(rtmpNow,"testName");
             nc.addEventListener(NetStatusEvent.NET_STATUS,getStream);
        }
 
        private function getStream(e:NetStatusEvent):void
        {
             good=e.info.code == "NetConnection.Connect.Success";
             if(good)
             {
                  // Here we call functions in our Java Application
                   responder=new Responder(streamNow);
		   r = new Responder(adder);
		   nc.call("addSomething",r,2,3);
                   nc.call("streamer",responder,"test");
             }
        }
 
		private function adder (obj:Object):void{
			trace("Total = ",obj.toString());
		}
 
        private function streamNow(streamSelect:Object):void
        {
             setCam();
             setMic();
             setVid();
	     trace("We've got our object",streamSelect.toString());
             switch(streamSelect.toString())
             {
                   case "left" :
                        outStream="left";
                        inStream="right";
                        break;
                   case "right" :
                        outStream="right";
                        inStream="left";
                        break;
             }
 
             //Publish local video
             netOut=new NetStream(nc);
             netOut.attachAudio(mic);
             netOut.attachCamera(cam);
             vidOut.attachCamera(cam);
             netOut.publish(outStream, "live");
 
             //Play streamed video
             netIn=new NetStream(nc);
             vidIn.attachNetStream(netIn);
             netIn.play(inStream);
        }
 
        private function setCam():void
        {
             cam=Camera.getCamera();
             cam.setMode(240,180,15);
             cam.setQuality(0,85);
        }
 
        private function setMic():void
        {
             mic=Microphone.getMicrophone();
             mic.rate=11;
             mic.setSilenceLevel(12,2000);
        }
 
        private function setVid():void
        {
             vidOut=new Video(240,180);
             addChild(vidOut);
             vidOut.x=25;
             vidOut.y=110;
 
             vidIn=new Video(240,180);
             addChild(vidIn);
             vidIn.x=vidOut.x+260;
             vidIn.y=110;
        }
  }
}

And that should be all of the pieces that you need to get a basic flash based, red5 using video chat up and running.

6 Responses to “Red5 Video Chat Example”

  1. martin says:

    thanks a lot for posting this! I will have a go at it will let you know how it went… cheers, martin

  2. daviddavis says:

    Can you go into more detail please in this section just to add the folder locations of the files to create and if you create a new flash project…

    Now that all of the server stuff is ready, create a new .fla file and in the properties inspector for the stage, go to Class and write whatever you want there. Since we’ve already named everything else Chatter we’ll stick with that. Flash will use the name of that class to look for .as files associated with it. Create a new file named “Chatter.as” (remember this name has to match whatever you named the class in Flash), and then put this actionscript in it:

  3. David says:

    But instead of using String objects is not any Object more fittable, I mean like an Object in red5 with methods and properties than let you use Stream capabilities, and how instantiate those with our NetStream.publish method!??

  4. sam says:

    nice example.

  5. kknd says:

    hi, thanks u example

    but i can’t to understand why Red5 can find and call a right Application funcation at webapps directory.
    i see the Red5 to know ApplicationAdapter is a Mbean use by JMX, but i don’t find Red5 to register and create the Mbean .

    if you know the secret,please send reason to me ,thank

  6. Catttdaddy says:

    Im not sure what I did wrong but I am getting errors using this code.

    I compiled the server app and created the Flash app.
    My client connects to the server but I get these errors in the console

    org.red5.server.Scope – Has child scope? Chatter in [GlobalScope@178a7d9 Depth= 0, Path = '', Name= default']
    org.red5.server.Scope – Child scope does not exist
    org.red5.server.net.rtmp.RTMPHandler – Scope Chatter not found on localhost

    Can anyone help me out with this?

Leave a Reply

Spam protection by WP Captcha-Free