Home >
Metadata and the Flash Platform
Readers of this site are familiar with the concept of metadata. Meta tags in websites for example, describe the site and the individual pages within. Files can also have meta data, this can be anything from where in the world that picture was taken and that kind of camera, to the track number of the mp3 your listening to.
Metadata has the ability to give us context to the media we are dealing with. It also makes for an interesting data source. Using the Flash Platform we can combine information all ready contained in files that you are using along with web services to create an interesting user experience.
This tutorial will combine id3 metadata from your mp3's to power an Flickr image search. Our final output will be an AIR application written with Flex Builder 3.
Getting Started
There are a few Actionscript libraries you will need to get this to work. If you read Developing Mashup Air Apps: Consuming Flickr Web Services then some of this will be familiar.
We need to grab as3flickrlib and as3corelib to get our example to work.
Once downloaded create a folder to unzip your files in.
One thing you can do is have a central folder for all your extra Actionscript libraries. This keeps you from having multiple copies of files over time. For example unzip them to a folder called "AsLib".
Get the key
Now that you have your Actionscript ready, you need a Flickr API key.
Head over to Flickr's API page and click on "Apply for a new API key".
Once you have the API key and the secret key hold on to them.
Now we can start building our project.
Start the build
Create a new AIR project in Flex Builder. My MXML file is called "MP3Mashup", feel free to call it something different.
Before we start scripting up our project, lets link up the downloaded library files to this project. Right click (control-click on a mac) on the project and in the properties go over to "Flex Builder Path".
The "Source path" tab should be open. From there click on the "Add folder" button and point to the source folder ("src") of first the as3coreLib folder then the as3flickrlib folder.
Now that all the parts are in place we can start coding everything up.
The first thing we will do is lay down some of our interface components.
Inside a VBox component we are going to add an HBox, a few buttons and a HSlider.
These components will have id's called openButton, playButton, stopButton, and volumeSlider
<mx:VBox>
<mx:HBox>
<mx:Button id="openButton" label="Open"/>
<mx:Button id="playButton" label="Pause"/>
<mx:Button id="stopButton" label="Stop"/>
<mx:HSlider id="volumeSlider" labels="Volume" minimum="0" maximum="1" snapInterval=".1" allowTrackClick="true" tickInterval=".1"/>
</mx:HBox>
</mx:VBox>
As you can see from the code example, each button was given a label, and the HSlider was given a few other attributes.
Since we are using this as volume control our minimum number is 0 and maximum is 1 to go with how Actionscript 3 deals with volume.
The other attributes allow for clicking on the slider, the number of ticks and how it will snap to the next tick.
Next with in the VBox container after our current HBox, we add a new HBox that will contain text fields. This way when the MP3 is loaded we can see "Artists", "Song Name" and "Album Name".
<mx:HBox>
<mx:Text id="songTitle"/>
<mx:Text id="artistName"/>
<mx:Text id="albumName"/>
</mx:HBox>
Finally we add an Image tag. When the results come back from Flickr they will get loaded here. The final version of your code should look like this:
<mx:VBox>
<mx:HBox>
<mx:Button id="openButton" label="Open"/>
<mx:Button id="playButton" label="Pause"/>
<mx:Button id="stopButton" label="Stop"/>
<mx:HSlider id="volumeSlider" labels="Volume" minimum="0" maximum="1" snapInterval=".1" allowTrackClick="true" tickInterval=".1"/>
</mx:HBox>
<mx:HBox>
<mx:Text id="songTitle"/>
<mx:Text id="artistName"/>
<mx:Text id="albumName"/>
</mx:HBox>
<mx:Image id="flickrImage" width="100%" height="100%"/>
</mx:VBox>
Import your classes and add some variables
Now that we have our simple layout done we can get into the code. Above our code but right below the WindowedApplication tag lets add a Script tag.
Within this script tag we will import some classes that will control our interface:
As you can see from the code below, we are importing some Actionscript classes. These classes gives us the ability to:
- load mp3 files selected from the operating system
- playing sound and controlling it's volume
- listen for events associated with the HScroller
- use a timer
- create a dropshadow
- allow the use of the policy XML file located on Flickr's website
<mx:Script>
<![CDATA[
import mx.events.SliderEvent;
import mx.events.FileEvent;
import flash.net.FileReference;
import flash.net.FileFilter;
import flash.net.FileReferenceList;
import flash.events.*;
import flash.system.SecurityDomain;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundTransform;
import flash.utils.Timer;
import flash.filters.DropShadowFilter;
Next we will import the Flickr library:
With this code we will have access to the Flickr API. All of the functions you find on the API page of the site will be available to you though Actionscript.
import com.adobe.webapis.flickr.*;
import com.adobe.webapis.flickr.events.FlickrResultEvent;;
import com.adobe.webapis.flickr.methodgroups.*;
Now we can create some variables to use in our application. The ones to note here are:
- _currentPosition: to remember the position (time) of sound when the pause button is clicked
- _isPlaying: so that our app knows if the sound is playing
- _soundFileReference: to reference the mp3 file that is loaded so volume and pausing will work
private var _sndChannel:SoundChannel;
private var _sndTransform:SoundTransform;
private var _currentPosition:Number;
private var _isPlaying:Boolean;
private var _sndFileReference:Sound;
private var _timer:Timer;
private var _shadow:DropShadowFilter;
Now we can add variables to control our Flickr results.
private var photoList:PagedPhotoList;
private var photoIndex:int;
private var _flickrObj:FlickrService;
private var _photos:Photos;
private var _photo:Photo;
private var _currentImageNum:int = 0;
private var _maxPhotos:Number = 120;
public static const FLICKR_URL:String = "http://www.flickr.com";
public static const CROSSDOMAIN_URL:String = "http://api.flickr.com/crossdomain.xml";
public static const API:String = "YOUR FLICKR API NUMBER";
public static const SECRET:String = "YOUR SECRET NUMBER"
We need Functions
Our functions will wire up our application. Add event listeners to our MXML components, get our timer to start working and get Flickr to recognize our account.
Lets start with our init function:
private function init():void{
_sndChannel = new SoundChannel;
_sndTransform = new SoundTransform();
_timer = new Timer(3000);
_timer.addEventListener(TimerEvent.TIMER, loadImages);
_shadow = new DropShadowFilter();
Security.loadPolicyFile(CROSSDOMAIN_URL);
_flickrObj = new FlickrService(API);
_flickrObj.secret = SECRET;
_flickrObj.addEventListener(FlickrResultEvent.AUTH_GET_FROB, getFrobHandler);
_flickrObj.addEventListener(FlickrResultEvent.PHOTOS_SEARCH, handleSearch);
_flickrObj.auth.getFrob();
volumeSlider.value = .5;
_sndTransform.volume = volumeSlider.value;
_sndChannel.soundTransform = _sndTransform;
volumeSlider.addEventListener(SliderEvent.CHANGE, changeVolume);
playButton.addEventListener(MouseEvent.CLICK, pauseSound);
stopButton.addEventListener(MouseEvent.CLICK, stopSound);
}
In our init function we have a few FlickrResultEvents that we are listening for. The first is for authorization. After we send all of our info over to Flickr, we just need to know we are connected. Once authorized, the rest of the application can work. The other two events sound pretty easy, we just need to see the results of a photo search then get information about the photos that have been returned.
Everything else in our function is pretty standard stuff. Add event listeners for our buttons, give our volumeSlider a value of .5 (remember our values are a range between 0 and 1) then give the sound the same value of the slider.
Our next three functions are pretty simple. First we want to make sure that Flickr authorized our connection so we can start to run our search.
private function getFrobHandler(evt:FlickrResultEvent):void{
trace(evt.success);
if(evt.success){
_conectedToFlickr = true;
}
}
Then we have our function that will let us change the volume based on our slider
private function changeVolume(evnt:SliderEvent):void{
//trace(evt.target.value);
_sndTransform.volume = evt.target.value;
_sndChannel.soundTransform = _sndTransform;
}
Then the browseFiles function will let our AIR application choose a mp3 file from the operating system, also in the open window show the words "Open MP3 File".
private function browseFiles(evt:MouseEvent):void{
var selectedFile:File = new File();
selectedFile.addEventListener(Event.SELECT, openFile);
selectedFile.browseForOpen("Open MP3 File");
}
Open File, Pause and Stop
The openFile function will now take the selected file and use it as part of our URLRequset to load up the MP3. In this function a few things happen.
First we cast our target a File datatype so we can then have access to nativeFilePath. Then we use a local sound object called "_soundObj". The reason that it is local is because, if you try to load an mp3 into a sound object that has previously loaded an mp3, you will get an error. To get around this, the object is local.
That is why "_sndFileReference" was made global, this way it will all ways remember the last mp3 file referenced in the application, giving us a way to do things like pause and perform our search.
The if statement within tries to simulate what a regular mp3 player would do. If the sound is not playing right now (position is 0) then load up the mp3 and start playing. In the case that a sound is currently playing, stop playing the sound load the new mp3 and start playing that one. In ether case keep the volume the same as the slider value, and when the sound is done playing, stop showing images also when it's done loading, show the metadata within the file.
Here is the code:
private function openFile(evt:Event):void{
var file:File = evt.target as File;
//trace(file.name);
//trace(file.nativePath)
var _soundObj:Sound = new Sound();
_sndFileReference = _soundObj;
if(_sndChannel.position == 0){
_soundObj.load(new URLRequest(file.nativePath));
_sndChannel = _soundObj.play();
_isPlaying = true;
}else{
_sndChannel.stop();
_soundObj.load(new URLRequest(file.nativePath));
_sndChannel = _soundObj.play();
_isPlaying = true;
}
_sndTransform.volume = volumeSlider.value;
_sndChannel.soundTransform = _sndTransform;
_sndChannel.addEventListener(Event.SOUND_COMPLETE, stopShowingImages);
_soundObj.addEventListener(Event.COMPLETE, showInfo);
}
The pauseSound function takes its orders based on the variable "_isPlaying". That variable should resemble the users experience, answering the question "is the sound playing?".
If the sound is playing then grab the current position of the sound (the current time) and hold on to it. Then stop playing sound though the channel. Stop the timer so we don't see any new images. Finally set our flag to "false" because our sound is no longer playing.
The second time you click on our pause button, you will run though this function again. However this time you will drop town to else (or I like to call "plan B"). Start playing that file again. Here is were having that file reference comes in handy because you remembered the information about the mp3 file that was previously loaded. Now not only do you want to start playing the sound but you want to play at what ever the last position was.
Here is the code with the stopSound function. The only thing interesting about that function is that it resets the way we keep track of the images. The variable "currentImageNum " lets you know what image in sequence you are looking at.
When stopping the sound we reset that to 0.
private function pauseSound(evt:MouseEvent):void{
if(_isPlaying == true){
_currentPosition = _sndChannel.position;
_sndChannel.stop();
_timer.stop();
_isPlaying = false;
}else{
_sndChannel = _sndFileReference.play(_currentPosition);
_sndTransform.volume = volumeSlider.value;
_sndChannel.soundTransform = _sndTransform;
_timer.start();
_isPlaying = true;
}
}
private function stopSound(evt:Event):void{
_sndChannel.stop();
_timer.stop();
_currentImageNum = 0;
}
Now we handle the metadata
This function is not that hard. The showInfo function is looking at the loaded mp3, if it can find the song name then fill the text fields with metadata and set "_currentImageNum" to 0 since we have not yet looked at any photos. In addition now that we have metadata go perform the Flickr search.
private function showInfo(evt:Event):void{
if(evt.target.id3.songName != null){
songTitle.text = "Song Name: " + evt.target.id3.songName;
artistName.text = "Artist: " + evt.target.id3.artist;
albumName.text = "Album: " + evt.target.id3.album;
getFlickrImages();
trace(evt.target.id3.songName);
_currentImageNum = 0;
}
}
There has been a lot of set up, but now we get to do our search. This function will take the metadata from the loaded mp3 file and use the artists name to search for images.
Note: Depending on the type and popularity of the artist, images will vary. This is a search of publicly available images on Flickr and not set up as a search of photos on any one personal account. The results are totally at the mercy of how they were tagged in Flickr. So in short... your millage may vary.
The getFlickrImages function checks first to see if we have an mp3 to get metadata from and if our Flickr authorization checks out. If both of these things are true then search all the images in Flickr based on artists name.
For the search there are a few parameters that can be added, the only two we will deal with, "tags" and "perpage". Here is the list if parmaters taken from the documentation:
- user id
- tags
- tag_mode - Either 'any' for an OR combination of tags, or 'all' for an AND combination. Defaults to 'any' if not specified
- text
- min_upload_date - Photos with an upload date greater than or equal to this value will be returned
- max_upload_date - Photos with an upload date less than or equal to this value will be returned
- min_taken_date - Photos with an taken date greater than or equal to this value will be returned
- max_taken_date - Photos with an taken date less than or equal to this value will be returned.
- license - The license id for photos (for possible values see the flickr.photos.licenses.getInfo method).
- extras - A comma-delimited list of extra information to fetch for each returned record.
- per_page - Number of photos to return per page.
- page - The page of results to return.
- sort - The order in which to sort returned photos.
private function getFlickrImages():void{
if(_sndFileReference != null && _conectedToFlickr == true){
_flickrObj.photos.search("",_sndFileReference.id3.artist, 'any', "", null, null, null, null, -1, '', _maxPhotos);
trace(_sndFileReference.id3.artist);
}
}
Our next function receives the results of the image search and puts them in a object called photoList. A PhotoList Object contains the photo property. That property has all the information about each photo being returned.
Once we have the images run the loadImage function.
private function handleSearch(evt:FlickrResultEvent):void{
//trace(evt.data.photos);
photoList = evt.data.photos as PagedPhotoList;
//trace(photoList.photos);
//trace(photoList.photos[0].id);
_totalPhotos = photoList.total;
loadImages();
}
The loadImages function does a few things. First thing that happens is that we get information about an image. Then our If statement checks to see if the current image (defined by our variable _currentImageNum) is not the last image in the list (defined by our variable _maxPhotos). In addition it makes sure that the information about the image does not come up "undefined".
When all that works out we then get the image from Flickr by adding up parts of the URL with data coming from the web service.
At the end we add the drop shadow filter and start the timer so that we can cycle though the results.
private function loadImages(evt:Event = null):void{
_photo = photoList.photos[_currentImageNum];
if(_currentImageNum != _maxPhotos && photoList.photos[_currentImageNum] != undefined){
//trace("_photo.id = " + _photo.id);
trace("_currentImageNum = " + _currentImageNum);
//trace("_totalPhotos = " + _totalPhotos);
trace("photoList.photos[_currentImageNum] = " + photoList.photos[_currentImageNum]);
flickrImage.source = 'http://farm' + _photo.farmId + '.static.flickr.com/' + _photo.server + '/' + _photo.id + '_' + _photo.secret + '.jpg';
flickrImage.filters = [_shadow];
if(!_timer.start()){
_timer.start();
}
}
_currentImageNum++;
}else{
_timer.stop();
}
}
Our final function just stops the images from showing up when the mp3 is done playing.
private function stopShowingImages(evt:Event):void{
_timer.stop();
}
Now that the whole thing is wired up we just need to run the init function at startup.
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()" width="800" height="600">
Conclusion
This is a very basic player. Once a design and transitions are applied it would make a nice mp3 player. The big takeaway is, metadata can be used for interesting things within a project and does not require any reliance to extra data sources. The use of metadata seems to be increasing. Flash CS4 has the ability to apply XMP data (Extensible Metadata Platform) inside FLA files from within the publish settings. For more information on XMP you can go to the XMP Developers Center at Adobe.com as well as the XMP Library for ActionScript on Adobe Labs. Finally a very interesting project that Thompson Reuters is working on with metadata is at Open Calais.com
MP3Mashup.zip




Facebook Application Development
Adobe should really consider a Publish to Android App feature as well. The Droid and Eris are really taking off fast, and by the time CS5 rolls out they will be in full effect. It may not be as immensely popular as the iPhone but well worth the attention.
I think your right. I would expect AIR to play a big part in something like that. Let Adobe do the code that talks to the hardware so we don't have to.
Hi,
True the Google Android Browser is the most powerful mobile browser I have used, but it is far from perfect.
yerba mate tea