Requirements

 

User level

Beginning

 

 

Additional Requirements

Flash components

  • ListBox
  • PushButton

Additional elements

  • Background graphics (optional)
  • MP3 audio files

There is a lot of talk on the web today about "streaming media." I prefer to divide that idea into two types because streaming media isn't just all about video: There's video streaming, using Flash Media Server to stream FLV files, and then there's audio streaming for something like MP3 files.

This article is about streaming MP3 files from Flash Media Server. The advantage of using Flash and Flash Media Server for streaming audio is that you can develop completely custom applications with your own branding (look at Odeo, for instance) while still providing a powerful, secure back end from which to stream files.

In this tutorial you will build a simple DJ application using Flash Professional 8 and Flash Media Server (see Figure 1). This application allows you to play MP3 files that are on the server to an audience—that is, anyone connected to the application. Audience members will hear the same song simultaneously, as though they are listening to a DJ playing tracks on the radio. You will start by including a set list of songs and defining two audio lines so that each song fades into the next. Then you'll create the interface for the application to allow the DJ to select the songs from the list and determine the play order. I'll also discuss how to add a microphone so that the DJ can talk to the audience. Because the authoring environment is Flash Professional 8, you can take advantage of the included components to control audio playback.

 

Building the server-side code

Before you get started, let's discuss the application you are building and define the desired functionality.

This project started out as a simple application designed to stream MP3 files to multiple computers. Rather than allowing random audio streaming—playing MP3 files that are sitting on the server—the goal of this application is to stream specific MP3 files to audience members, as though they are listening to a DJ on a radio station. To achieve this goal, you'll need to set up the DJ application as a remote audio controller that dictates when the MP3 files will play on the server, and have your audience (the users connected to the application) subscribe to the audio stream.

By configuring the application in this fashion, the songs heard by the audience members should be synced up (depending on the speed of their Internet connection) and all connected users will hear the same song. As you go through the steps to create this application, I'll cover important information regarding server-side streams, server-side shared objects, and how to access them both from the client side. You will also learn why it is helpful to use this approach when building this type of application.

To begin, you'll need to establish a set list of songs (MP3 files); you'll also need to set up two lines of audio output. The two lines are created to add transitions between the songs, fading the previous song out while fading the next song in. The list of songs displayed in the application's interface allow the DJ to choose which MP3 files to play and to set each selected song into one of the two audio output lines. As an extra bonus, I'll show you how to add a microphone so that the DJ has the ability to talk directly to the audience over the stream.

It is important to note that the interface of the completed application will be controlled by the DJ. The selection of songs, use of the microphone, and audio output levels are determined by the interface settings. In contrast, the audience members are merely listening, as though they are turning on a radio. Their participation involves opening the application, connecting to the stream, and listening to what is playing.

Let's get started. Begin by gathering a folder of MP3 files. Once you have the files, the next step is to place them on the server. To accomplish this, there are two choices. The first option is to put the MP3 files into a "streams" folder in the application you are going to connect to, to make the songs available for playback. The second option involves putting the MP3 files into a virtual folder that could be anywhere on the server. For the purpose of this tutorial, let's put the audio files in the streams folder inside the application folder.

Call the application folder mp3dj and create it in the applications directory of FMS. Inside the mp3dj folder, create a folder called streams. Then create a subfolder inside the streams folder called mp3djapp that contains your collection of MP3 files (see Figure 2).

Now that the directories are set up, you can start writing the server-side code. At this stage of development, I usually create a barebones version of the server-side code first, so that I can test the code from the client side later. It is a common practice to write the majority of code in one file (the main.asc file) and then later separate the code into reusable chunks and files like classes. For the purposes of this simple application, I will keep the code all in one file. In a real-world development process, however, the code would be split up, commented, and organized once the desired functionality was achieved. Separating sections of code into different scripts can also make it easier to manage the project and build upon it later.

 

Creating the main.asc file (server-side file)

Use your favorite ActionScript editor to create an .asc file. You can use Flash 8 or Dreamweaver 8 to create this file. Simply save a text file with the ASC file extension.

First things first. Tell the application what to do when it starts up. The code for this is located in the onAppStart function of the Application class. Comments are included in the script below to clarify what is happening:

 

application.onAppStart = function(){ //create a stream for each "line" on the server-side. The audience will subscribe to these streams to listen this.djStream1 = Stream.get("dj1"); this.djStream2 = Stream.get("dj2"); //track the users by establishing a userID this.userID = 0; //track the volume of the server-side lines for ease in control this.dj1Volume = 0; this.dj2Volume = 0; //the shared object that holds the volume values and updates all clients this.volSO = SharedObject.get("vol", false); //track the number of current users this.userCount = 0; }

 

Next, deal with the users as they connect to our DJ application. This script would be more secure if you added code that checks for malicious users, but that is beyond the scope of this article. Read the comments to get a better understanding of each line in the script below:

 

application.onConnect = function(clientObj){ //accept them for now application.acceptConnection(clientObj); //set their userID to their client object clientObj.userID = this.userID; //tell them they are ready to start listening and pass in the volumes of the lines clientObj.call("setUserID", null, clientObj.userID, application.dj1Volume, application.dj2Volume); //up the userID to make a new unique ID for the next one this.userID ++; //up the usercount this.userCount++; //broadcast a message to all clients of the new usercount minus one because we don't need to count the DJ application.broadcastMsg("setUserCount", this.userCount - 1); }

 

If you happen to be a recent convert to Flash Media Server 2 from Flash Communication Server 1.5, you might notice that you are using the broadcastMsg()
method of the Application class. This is handy because it will go through all the clients that are connected to the application and send them something. In this case, you want to call a function called setUserCount. This strategy eliminates the need to loop through all the connected clients manually.

Next set what the application should do when an audience member disconnects. At this point there isn't much going on, but you need to use the broadcastMsg() function again to tell the connected users that there is one less user listening in:

 

application.onDisconnect = function(){ //reduce the usercount this.userCount--; //failsafe way to make sure we don't go into the minuses if(this.userCount < 0){ this.userCount = 0; } //broadcast a message to all clients of the new usercount application.broadcastMsg("setUserCount", this.userCount - 1); }

 

Now it's time to set up the functions that you want the DJ to be able to call to, which will tell all the clients what to do. Since there are two lines that we want audience members to be able to listen to (as the DJ mixes songs) you need two functions that will change the song that is playing in each of these. These first two functions are almost exactly the same; they just refer to different stream names.

In this case, you have set the functions up on the Client object as a prototype function. You could also choose to attach each function to the client object as they connect up. Personally I find this method is easier. It's not a big issue in this application to apply the functions to every single client that connects to your application in a blanket prototype style. However, if you are building an application where you need to apply many functions to one client (like an administrator of the application) then it is best just to attach the functions directly to that specific client. That approach is also more secure because it prevents potentially malicious users from calling functions within your application.

At this point I've switched styles of informing clients of a certain action. In the code below, I use the send() method of a shared object to communicate the volume of the streams to the clients. This is another good tool to have in your developer box—because it allows you to do certain things, such as call a function on the client side from the server. When you use the send() method, you are calling a function that is attached to the shared object on the client side if that client is connected to the shared object. In this case, if the user isn't subscribed to the shared object and doesn't have the function declared, then the function won't get called for the user. You could use the broadcastMsg() method again but I prefer to separate the functionality in this way to improve the application's performance:

 

Client.prototype.setDJ1Song = function(song){ //setting a song here, stop playing the previous one application.djStream1.play(false); //play the MP3 file application.djStream1.play("mp3:" + song, 0, -1 ,true); } Client.prototype.setDJ2Song = function(song){ //setting a song here, stop playing the previous one application.djStream2.play(false); //play the MP3 file application.djStream2.play("mp3:" + song, 0, -1 ,true); } //when the DJ changes the volume of the lines then this function is called Client.prototype.setVolumeOfLines = function(v1, v2){ //set the server side values application.dj1Volume = v1; application.dj2Volume = v2; //send the values to all the users that are subscribed to the shared object application.volSO.send("setVolumeOfLines", v1, v2); }

 

This completes the server-side code for the DJ application. Now let's look at the client side and add the features for the DJ interface.

 

Building the DJ controls for the application (client-side interface)

This section creates the client side of the application—the area controlled by the DJ. The graphic design for the application can be anything you desire; use any layout you prefer. The only requirements are to include the following components:

  • Three ListBox items:
    • The first list box displays the set list of songs
    • The second list box displays the song in line 1
    • The third list box displays the song in line 2
  • PushButton items to add and remove songs from the lines:
    • Add two buttons under the main set list to add songs to each of the lines
    • Add two buttons under each of the lines to play (set) and remove songs
  • PushButton for the microphone and a slider to change the volume between the two lists:
    • For this example, you create your own scrub bar and scrubber

You can design the layout of the application as you prefer and in any configuration desired, as long as you include the items listed above.

Take a look at the code and see what is happening behind the scenes. Rather than spelling out the button names and dictating the code to be written, you may find that you understand this next part more quickly by selecting each button and noticing their instance names, and then looking for those instance names in the code below. If you download the sample files and open the FLA file for the DJ side, you'll see the code on frame 1. Let's take a moment to review it.

Right away you will notice that you are setting a theme color for the components. This is a cosmetic setting to match your design; it is optional. Draw your attention to the next set of lines in the code. Here you are setting variables that you will use later to make your connections and subscriptions to the MP3 streams. I've added comments in the code below to make it easier for you to understand what is going on:

 

_global.style.setStyle("themeColor", 0xFF0000);//set the color to red //creating the net connection object here var nc:NetConnection = new NetConnection(); //the userID will be set when the server is ready to handle us var userID:Number; //our two sound objects to handle the streams' volume var dj1Sound:Sound; var dj2Sound:Sound; //a boolean value to track if we are using the microphone or not var sendingMicAudio:Boolean = false; //this is the microphone object var mic:Microphone; //we will set the onStatus event of the netConnection object, purely for debugging purposes right now nc.onStatus = function(info) { trace(info.code); }; //this function will be called from the server-side when it is ready to handle us, this is how we know we can go get the streams nc.setUserID = function(id) { userID = id; getStream(); }; //the function that is called from the server-side using the broadcastMsg() method nc.setUserCount = function(userCount) { if (userCount == 1) { userCountText.text = userCount+" user listening"; } else { userCountText.text = userCount+" users listening"; } }; //this is the function that will get the streams. Since we are all connected now it'd be nice to listen to the radio function getStream() { //these next two objects are for the streams ns1 = new NetStream(nc); ns1.setBufferTime(3); ns2 = new NetStream(nc); ns2.setBufferTime(3); micNS = new NetStream(nc); _root.createEmptyMovieClip("dj1", 1); dj1.attachAudio(ns1); dj1Sound = new Sound(dj1); _root.createEmptyMovieClip("dj2", 2); dj2.attachAudio(ns2); dj2Sound = new Sound(dj2); setVolumeOfLines(); ns1.play("dj1", -1, -1); ns2.play("dj2", -1, -1); } //setting up the buttons: the buttons will do different things depending on which is pushed buttonListener = new Object(); buttonListener.click = function(event) { var b = event.target._name; switch (b) { case "setSong1Button" : var song = songList1.selectedItem.label; nc.call("setDJ1Song", null, song); break; case "setSong2Button" : var song = songList2.selectedItem.label; nc.call("setDJ2Song", null, song); break; case "removeSong1Button" : var song = songList1.selectedIndex; songList1.removeItemAt(song); if (songList1.length == 0) { //stop the current song on the server nc.call("setDJ1Song", null, null); } break; case "removeSong2Button" : var song = songList2.selectedIndex; songList2.removeItemAt(song); if (songList2.length == 0) { //stop the current song on the server nc.call("setDJ2Song", null, null); } break; case "addDJ1Song" : var song = mainSongList.selectedItem.label; songList1.addItem(song); break; case "addDJ2Song" : var song = mainSongList.selectedItem.label; songList2.addItem(song); break; case "sendMic" : sendMicAudio(); break; } }; //this function will be called when we press the mic button function sendMicAudio() { if (sendingMicAudio == false) { sendMic.label = "Stop Mic"; mic = Microphone.get(); mic.setSilenceLevel(25, 2000); mic.setRate(22); micNS.attachAudio(mic); micNS.publish("DJ", "LIVE"); sendingMicAudio = true; } else { sendMic.label = "Start Mic"; micNS.attachAudio(null); micNS.publish(false); sendingMicAudio = false; } } //this function looks a bit mathematical, but all we are doing is splitting the difference //between the two lines and the length of the scrub bar based on where the scrubber is //this sends two values to the server, which are then sent to all the listeners //this is where we will be calling the setVolumeOfLines() function on the server function setVolumeOfLines() { var dj1volume = volumeBar._x-volumeScrub._x+100+50; var dj2volume = volumeScrub._x-(volumeBar._x+volumeBar._width/2)+50; if (dj1volume<0) { dj1volume = 0; } if (dj2volume<0) { dj2volume = 0; } dj1Sound.setVolume(dj1volume); dj2Sound.setVolume(dj2volume); nc.call("setVolumeOfLines", null, dj1volume, dj2volume); } //setting up all the listeners for the buttons setSong1Button.addEventListener("click", buttonListener); setSong2Button.addEventListener("click", buttonListener); removeSong1Button.addEventListener("click", buttonListener); removeSong2Button.addEventListener("click", buttonListener); addDJ1Song.addEventListener("click", buttonListener); addDJ2Song.addEventListener("click", buttonListener); sendMic.addEventListener("click", buttonListener); //adding all the songs to the mainSongList listbox mainSongList.addItem("Love Train"); mainSongList.addItem("Now That We Found Love"); mainSongList.addItem("Ooh Wee"); mainSongList.addItem("Good Enough"); mainSongList.addItem("Use The Force"); mainSongList.addItem("Everybody Wants To Rule The World"); mainSongList.addItem("We Got The Beat"); mainSongList.addItem("Turning Japanese"); mainSongList.addItem("In Da Club"); mainSongList.addItem("Vertigo"); mainSongList.addItem("No Sleep Till Brooklyn"); mainSongList.addItem("Gettin' Jiggy Wit It"); mainSongList.addItem("Hey Ya!"); mainSongList.addItem("Flavour Of The Week"); mainSongList.addItem("Hit That"); mainSongList.addItem("Rain Man"); //this is for the scrubber for the volume knob, we set the volume of both lines here volumeScrub.onPress = function() { this.startDrag(false, volumeBar._x, this._y, volumeBar._x+volumeBar._width, this._y); this.onMouseMove = function() { _root.setVolumeOfLines(); }; }; volumeScrub.onRelease = volumeScrub.onReleaseOutside=function () { stopDrag(); delete this.onMouseMove; }; //connect to the server! nc.connect("rtmp://127.0.0.1/mp3dj/mp3djapp");

 

And that's it! Well that is a bit of code, so let's discuss what is happening. First we'll look at the functions that some of the buttons are calling. In the code above, you'll see this function:

 

nc.call("setDJ1Song", null, song);

The example above is calling the function that you previously created on the server side to set a song for line 1. You get the song value from a selection made in the main list. This is being called on the netConnection object nc. When you make a call on the netConnection object, it calls the functions that are attached to the Client
object on the server side.

Also notice the function used to send the microphone out to all the users. The listener side subscribes to the DJ stream because it is going to be a live stream straight from this client-side application. You are now publishing a stream from here and it is set to be "live." When you wish to stop the stream, just set the publish value to false :

function sendMicAudio() { if (sendingMicAudio == false) { sendMic.label = "Stop Mic"; mic = Microphone.get(); mic.setSilenceLevel(25, 2000); mic.setRate(22); micNS.attachAudio(mic); micNS.publish("DJ", "LIVE"); sendingMicAudio = true; } else { sendMic.label = "Start Mic"; micNS.attachAudio(null); micNS.publish(false); sendingMicAudio = false; } }

 

It's important to understand how this application is connecting to the server and what happens. This occurs on the last line:

 

nc.connect("rtmp://127.0.0.1/mp3dj/mp3djapp");

 

In this line, you set the server IP number. You can also use a domain name if it's the same. The IP number 127.0.0.1 relates to any person's local computer. Next, you reference the application name (which you have set to "mp3dj") and then we reference the instance name "mp3djapp." This mirrors the hierarchy of the directories and the location of the stream files you set up at the beginning of this tutorial.

Now you are ready to move on to the listener side of the application.

 

Building the listener side of the application

This next part is pretty straightforward. All you need to do is play the streams and adjust the volume as the DJ changes the settings.

You are adding features similar to those you used on the DJ side, so this code may look repetitive. The main thing to remember here is to make sure the server is ready before allowing the connection to the streams and before attempting to play them. In the code below, the volume will be set by the DJ using the interface. This is the reason you will be connecting to the shared object on the server:

 

//create the net connection object var nc:NetConnection = new NetConnection(); //this variable is set by the server when it is ready for us var userID:Number; //the two sound objects to control the volume of the streams var dj1Sound:Sound; var dj2Sound:Sound; //the sound object for the DJ microphone var DJ:Sound; nc.onStatus = function(info) { trace(info.code); }; //this function will be called by the server and will pass in the stream volumes nc.setUserID = function(id, line1, line2) { userID = id; getTheSO(); getStream(line1, line2); }; //get the shared object so we can subscribe to the setVolumeOfLines function //to set the volume of the lines from the DJ function getTheSO() { volumeSO = SharedObject.getRemote("vol", nc.uri, false); volumeSO.setVolumeOfLines = setVolumeOfLines; volumeSO.connect(nc); } //this will be called when we are ready to talk to the server //we just set up all of our netstreams and sounds here function getStream(line1, line2) { ns1 = new NetStream(nc); ns1.setBufferTime(1); ns2 = new NetStream(nc); ns2.setBufferTime(1); micNS = new NetStream(nc); _root.createEmptyMovieClip("DJmic", 1); DJmic.attachAudio(micNS); DJ = new Sound(DJmic); micNS.play("DJ"); _root.createEmptyMovieClip("dj1", 2); dj1.attachAudio(ns1); dj1Sound = new Sound(dj1); _root.createEmptyMovieClip("dj2", 3); dj2.attachAudio(ns2); dj2Sound = new Sound(dj2); setVolumeOfLines(line1, line2); ns1.play("dj1", -1, -1); ns2.play("dj2", -1, -1); } //this function will get called from the server function setVolumeOfLines(line1, line2) { dj1Sound.setVolume(line1); dj2Sound.setVolume(line2); //line1Vol.text = line1; //line2Vol.text = line2; } //connect to the server! nc.connect("rtmp://127.0.0.1/mp3dj/mp3djapp");

 

There is literally nothing to add to the stage at this point, so I didn't include a FLA in the sample files that contains the code above. To see how it works, simply copy and paste this code into a new FLA file and test the movie.

I've covered all the basics to make this DJ application function as expected. There are many other things that should be addressed before placing this application in a production environment—security is still an issue. But for the purposes of this tutorial, you've seen how to work with streams and set up a prototype application.

 

Where to go from here

Although this prototype works as expected, there are several other areas of development that you should consider before deploying this application. The first thing to address involves improving its security. It is very important to lock out potentially malicious users from accessing your application and doing harm. Although adding security to the DJ application lies outside the scope of this tutorial, be sure to consult the Flash Help documentation to learn more about making your applications secure. The Security Topic Center in the Developer Center also provides useful information on this topic.

In addition to making the application more secure, there are many features that could be added to make this DJ application more robust. For example, the volume knobs could be split to work independently, but could also include a check box to link them together. If the DJ selects the link volume option, one volume knob would automatically move up as the other is manually turned down.

Since the songs will be fading from one to the next, you could also create a master "fade out" button to help control the audio levels. There are many other features you could add to control the output of sound, such as adding a volume knob to control the microphone level.

Depending on the level of complexity, you may decide to organize the code into different areas, rather than keeping all of the scripts in the main.asc file. Separating the code into different sections and making the code modular makes it easier to read as you add new features. It is also very helpful to add comments to your code to describe what is happening, especially if you are working on a team.

This tutorial highlights some of the important methods used to control the audio playback to application users from another user, as well as how to make calls to functions on the server and vice versa. The main concepts to take away are how to use streams—not only on the server but also from the client side sending out a live stream—and how to play them on another stream. Hopefully this sample code will serve as a starting point as you begin developing applications that utilize streaming media.

 

 


More Like This

Tutorials & Samples