Creating an Adobe AIR Slide Show

The “Hack Ack” topic for the 2010 e-Learning Authoring Conference was Rock & Roll. I thought it would be fun to create a “Slide Show” application that would randomly grab all photos from a directory and display them. At the same time, it would randomly pick music files out of another directory, starting the next one as soon as the current one is finished. I decided to build my first incarnation in Flash/Flex (see the next post for a Windows Presentation Foundation example). But the security model of normal Flash is such that you cannot read the contents of directories on the user’s hard drive without user interaction. Adobe AIR applications don’t have that restriction, so I decided to tackle my first AIR application. The complete application can be downloaded as part of the Archives for Attendees, but we will look at some of the highlights.

The basic design is to read all the photos into one collection and all the music into another. We randomly grab a music file and start playing it. We handle its “completed” event and launch a new one when the music file is finished. We then start a timer to use with the photos. When the timer fires, we randomly pick a photo, remove it from the collection, and display the photo. To store information between timer cycles, we need to declare some variables outside a function block as shown below.

// configuration constant
private var photoDelay:int = 3000; // milliseconds

// shared variables
private var photoArrayCollection:ArrayCollection;
private var musicArrayCollection:ArrayCollection;
private var timerId:Timer;
private var soundChannelId:SoundChannel;

We use photoDelay in our timer. The two ArrayCollection variables allow us to store our list of photos and music respectively. The timerId variable stores the reference to our timer and the soundChannelId is the SoundChannel that we use to play our music.\

When the application starts, we call the configureAppfunction as shown below:

private function configureApp():void {
	// get list of media and photos
	var currentDirectory:File = File.applicationDirectory;
	var photoDirectory:File = currentDirectory.resolvePath("photos");
	var musicDirectory:File = currentDirectory.resolvePath("music");
	var photoArray:Array = photoDirectory.getDirectoryListing();
	var musicArray:Array = musicDirectory.getDirectoryListing();

	// use ArrayCollection so we can remove items easily after they are used
	photoArrayCollection = new ArrayCollection(photoArray);
	musicArrayCollection = new ArrayCollection(musicArray);
}

We use the AIR File class to find our two subdirectories (“photos” and “music”) and then get a listing of all the files in them. We store them in our associated photoArrayCollection and musicArrayCollection variables.

The code for the “Start” button is shown below:

protected function startBtn_clickHandler(event:MouseEvent):void
{
	// start timer and begin playing sound
	timerId = new Timer(photoDelay, photoArrayCollection.length); // once per photo
	timerId.addEventListener(TimerEvent.TIMER, timerHandler);
	timerId.start();

	playSound();		
}

We create our timer using our photoDelay variable (3000 milliseconds). The second parameter is the number of times that we want the timer to fire. We use the number of photos that we have. This is nice in that we don’t then have to stop the timer. The next line is where we tell Flash/Flex what function (timerHandler) to call when the timer fires. We then start the timer and call the playSoundfunction below.

private function playSound():void {
	if (musicArrayCollection.length > 0) {
		// get a random number between 0 and the length of the photoArrayCollection. 
		// Then show that photo and remove the item from the collection
		var rand:Number = Math.random();
		var indexNum:int = Math.round(rand * (musicArrayCollection.length - 1));
		var musicFileId:File = musicArrayCollection[indexNum] as File;
		var musicPath:String = musicFileId.nativePath;

		var soundId:Sound = new Sound();
		var requestId:URLRequest = new URLRequest(musicPath);

		soundId.load(requestId);
		if (soundChannelId != null) {
			soundChannelId.stop();
		}
		soundChannelId = soundId.play();
		soundChannelId.addEventListener(Event.SOUND_COMPLETE, soundCompletedHandler);	
		musicArrayCollection.removeItemAt(indexNum);				
		status = "Playing " + musicFileId.name + ". indexNum = " + indexNum + 
		    ". length = " + musicArrayCollection.length;
	}
}

private function soundCompletedHandler(e:Event):void {
	playSound();
}

We first check to make sure that we have music files to play. If so, we use the Math.random() function to get a random number between 0 and 1. We then multiply that by the number of sound files (we subtract 1 since we are creating an index that starts from 0). We then round the number so that we don’t have a decimal number. From there, we go to our musicArrayCollection and grab the associated sound file. We read its nativePath property to get its complete path. We create a Sound object and a URLRequest to actually read the file. We load sound and then check to see if a previous sound is playing. If so, we use the SoundChannel to stop it (that’s why we needed to save a reference to soundChannelId). We play the sound, which gives us the SoundChannel. We call the soundCompleted function when the music file finishes. Notice that this function just calls playSound again. Very importantly, we remove the file from the musicArrayCollection. This is how we avoid playing the file again and how we know to stop when all the files have been played. Finally, we set the status property, which causes the information on the name, index, and length to show up on the application’s status bar.
The last piece of the puzzle is displaying the photos. That happens in the timerHandler function shown below:

private function timerHandler(e:TimerEvent):void {
	var rand:Number = Math.random();
	var indexNum:int = Math.round(rand * (photoArrayCollection.length - 1));
	var photoFileId:File = photoArrayCollection[indexNum] as File;
	var photoPath:String = photoFileId.nativePath;

	photoImage.source = photoPath;	
	photoArrayCollection.removeItemAt(indexNum);	
	status = "Displaying " + photoFileId.name + ". indexNum = " + indexNum + 
        ". length = " + photoArrayCollection.length;
}

This logic is very similar to that used to play the music files. We again get a random number between 0 and the number of photos still available (-1 to account for the fact that we are starting at 0). We get the complete path and just set the source property of our Image control to that path. We remove the file from the collection and update our status bar.

If you would like to see how to implement this application in Windows Presentation Foundation and Visual Basic, see the next post.

Advertisements

About Jeff Rhodes
Jeff Rhodes is the Chief Technical Officer and owner of Platte Canyon Multimedia Software Corporation, a leader in developing commercial software that Improves the Lives of Training Developers. He graduated at the top of his class at the Air Force Academy, where he earned a Bachelor of Science in Electrical Engineering. Jeff received a Masters degree in Economics from the London School of Economics, which he attended under a British Marshall Scholarship. Jeff is the author of "Programming for e-Learning Developers: ToolBook, Flash, JavaScript, & Silverlight" and "VBTrain.Net: Creating Computer and Web Based Training with Visual Basic .NET." He also co-wrote "The ToolBook Companion." He has had numerous articles on training development published and is a frequent presenter at conferences both in the U.S. and Europe. Jeff lives in Colorado Springs with his wife Sue and sons Derek and Michael.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: