AS3 Lyricsfly API

The other day I went over to my girlfriend’s brothers house for a family tree trimming. As we sat around drinking wine, streaming Somafm’s Christmas lounge, an actionscript idea popped into my head. As I listened to the unfamiliar music I was concentrating on the lyrics of each song. Now, I know the classics, but they played a few I just had never heard in my entire life. Out of curiosity I went over to their computer and started to look up the lyrics online. Low and behold there they were. You can find almost any artist’s lyrical composition online. So, my idea came to full fruition, create an online video karaoke website. Well, what does that mean?

I started by searching the internet for Api’s that I could obtain information from. First stop YouTube, they have an API where I can retrieve music in video form. Second stop ProgrammableWeb.com a directory and community for API mashups. I searched the database and found the only 2 providers of lyrical content. Well, I should say one, since LyricWiki no longer provides an API due to pending lawsuits. So I had to settle with Lyricsfly.

LyricsFly boasts to have dead on correct content, and they enable you to start building your app with a temporary API key. So, below is a test app which allows you to contact Lyricsfly API and returns data.
There are a few items I wish Lyricsfly had enabled with thier API.

WISH LIST:
Allow the user to search for an artist only, which returns a list of Titles by that artist.
Allow a user to search for a Title only and return a list of Titles with Artist name.
Allow the user to search for an Artist and if the result is empty return possible artist names.
Get the Lyrics feature to work returning possible Artist and Title info.

The search by lyric feature never seems to work. I always get an empty query result, even when I try the feature from Lyricsfly’s own website. So, that API call is a dud!
The search by artist and title feature works, if you spell the Artist name correctly including the Title. Some people are horrible at spelling artist names or usually only know one of the variables, so this can be problematic when programming my app.

The idea behind video karaoke is that user could watch a video and sing along, because the lyrics would be displayed also. The app would enable users to search YouTube for music videos and select the Title from the results. The app would then search the lyric API for the lyrics, finding a match and preparing the user for their online karaoke debut.
Below is the test app for the lyrics.

Get Adobe Flash player

Here’s the Document class:
LyricApi.as
/****************************
* Manuel Gonzalez           *
* design@stheory.com        *
* www.stheory.com           *
* www.codingcolor.com       *
*****************************/

package {

import com.stheory.app.business.*;
import flash.events.Event;
import flash.display.*;
import fl.controls.Button;
import fl.controls.RadioButton;
import fl.controls.TextInput;
import fl.controls.TextArea;
import flash.net.navigateToURL;
import flash.net.URLRequest;
import flash.text.TextFormat;
import flash.events.MouseEvent;

public class LyricApi extends Sprite {
private var b1:Button;
private var ti1:TextInput;
private var ti2:TextInput;
private var ti3:TextInput;
private var rb1:RadioButton;
private var rb2:RadioButton;
private var ta:TextArea;
private var lsf:LyricsFlyServices;
private var defaultArtist:String = "Jay-Z";
private var defaultTitle:String = "99 Problems";
private var defaultLyricsOfSong:String = "Havin' girl problems I feel bad for you son";
private var url:String = "http://lyricsfly.com/api/";
private var apiKey:String = "c00397e4003317fdd-temporary.API.access"
private var keyExpiredInfo:String ="The weekly API KEY has expired or is invalid!\rPlease click on the logo below to obtain a new key.\rYou will find it underneath the documentaion header.\rCopy it and paste it into the ApiKey Input field,and try again.";
private var keyExpired:Boolean = false;

public function LyricApi() {
setupInputFields();
createRadioButton();
setupSearchButton();
t_area1.text = defaultLyricsOfSong;
lsf = LyricsFlyServices.getInstance();
lsf.key = apiKey;
lsf.addEventListener(LyricsFlyEvent.DATA_LOADED,printOutput);
lsf.addEventListener(LyricsFlyEvent.DATA_ERROR,printOutput);
logo.addEventListener(MouseEvent.CLICK, logoClick);
logo.buttonMode = true;
}
/*
Method: createRadioButton()
Parameters:
Returns:
*/

private function createRadioButton():void {
rb1= new RadioButton();
rb1.label="Artist & Title";
rb1.selected = true;
rb1.move(50, 180);
rb2= new RadioButton();
rb2.label="Lyrics";
rb2.selected = false;
rb2.move(275, 180);
addChild(rb1);
addChild(rb2);
}
/*
Method: updateKeyInfo
Parameters:
event:Event
Returns:
*/

private function updateKeyInfo(event:Event):void {
lsf.key=ti1.text;
}
/*
Method: setupInputFields
Parameters:
Returns:
*/

private function setupInputFields():void {
ti1 = new TextInput();
ti1.text = apiKey;
ti1.addEventListener(Event.CHANGE,updateKeyInfo);
ti2 = new TextInput();
ti2.text = defaultArtist;
ti3 = new TextInput();
ti3.text = defaultTitle;
ti1.move(62,7);
ti1.width = 250;
ti2.move(50,60);
ti2.width = 200;
ti3.move(50,90);
ti3.width = 200;
addChild(ti1);
addChild(ti2);
addChild(ti3);
}
/*
Method: setupSearchButton
Parameters:
Returns:
*/

private function setupSearchButton():void {
b1 = new Button();
b1.x = 425;
b1.y = 180;
b1.label = "Search";
b1.addEventListener(MouseEvent.CLICK, buttonClick);
addChild(b1);
}
/*
Method: buttonClick
Parameters:
event:MouseEvent
Returns:
*/

private function buttonClick(event:MouseEvent):void {
if (rb1.selected) {
if (!isEmpty(ti2.text) && !isEmpty(ti3.text)) {
lsf.searchByArtistAndTitle(unescape(ti2.text),unescape(ti3.text));
formatTextArea();
} else {
formatTextArea(0xFF0000);
t_area2.text = "Both ARTIST field and TITLE filed must be filled";
}
} else if (rb2.selected) {
lsf.searchByLyrics(unescape(t_area1.text));
}
}
/*
Method:formatTextArea
Parameters:
inColor:Number
Returns:
*/

private function formatTextArea(inColor:Number=0x000000):void {
var textFormat:TextFormat=new TextFormat();
textFormat.color = inColor;
t_area2.setStyle("textFormat", textFormat);
}
/*
Method:
Parameters:
Returns:
*/

private function logoClick(e:MouseEvent):void {
var request:URLRequest = new URLRequest(url);
navigateToURL(request,"_blank");
}
/*
Method:printOutput
Parameters:
event:LyricsFlyEvent
Returns:
*/

private function printOutput(event:LyricsFlyEvent):void {
if (event.data.result) {
formatTextArea();
t_area2.text = event.data.lyrics;
} else if (event.data.errorCode =="401" ) {
formatTextArea(0xFF0000);
t_area2.text = keyExpiredInfo;
keyExpired = true;
} else {
formatTextArea(0xFF0000);
t_area2.text = event.data.msg;
}
}
/*
Method:isEmpty
Parameters:
p_string:String
Returns:Boolean
*/

private function isEmpty(p_string:String):Boolean {
if (p_string == null) {
return true;
}
return ! p_string.length;
}
}
}

Here’s the class which handles the Lyricsfly API.:

LyricsFlyServices.as

/****************************
* Manuel Gonzalez           *
* design@stheory.com        *
* www.stheory.com           *
* www.codingcolor.com       *
*****************************/

package com.stheory.app.business{

import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.net.URLRequestMethod;
import flash.net.URLVariables;
import flash.system.Security;
import com.stheory.app.business.LyricsFlyEvent;
public class LyricsFlyServices extends EventDispatcher {

private static  var _instance:LyricsFlyServices;
private var _userID:String;
private var _apiBaseURL:String = "http://api.lyricsfly.com/api";
private var _UrlLoader:URLLoader;
private var _parser:LyricsDataParser;
public function set key(inStr:String):void
{
_userID = inStr;
}
public function get key():String
{
return _userID;
}
public function LyricsFlyServices(singletonEnforcer:SingletonEnforcer) {
}
/*
 Method: getInstance
 Parameters:
 Returns: LyricsFlyServices
 */

public static function getInstance():LyricsFlyServices {
if (_instance == null) {
_instance = new LyricsFlyServices(new SingletonEnforcer());
_instance.onInit();
}
return _instance;
}

/*
Method: onInit
Parameters:
Returns:
*/

private function onInit():void {
_parser = new LyricsDataParser();
loadLyricsPolicyFile();
}
/*
Method: loadLyricsPolicyFile
Parameters:
Returns:
*/

private function loadLyricsPolicyFile():void {
Security.loadPolicyFile("http://lyricsfly.com/crossdomain.xml");
}
/*
Method: addProductToCart
 Parameters:
 inProductId : String
Returns:  
 */

public function searchByArtistAndTitle(inArtistName:String,inSongTitle:String):void {
var url:String = _apiBaseURL + "/api.php?i="+_userID+"&a="+inArtistName+"&t="+inSongTitle+"";
var urlRequest:URLRequest = new URLRequest(url);
_UrlLoader = new URLLoader();
_UrlLoader.addEventListener(Event.COMPLETE, onSearchArtistAndTitleComplete,false,0,true);
_UrlLoader.addEventListener(IOErrorEvent.IO_ERROR, onSearchArtistAndTitleError,false,0,true);
_UrlLoader.load(urlRequest);
urlRequest = null;
}
/*
Method: addProductToCart
Parameters:
inProductId : String
Returns:  
 */

public function searchByLyrics(inLyrics:String):void {
var url:String = _apiBaseURL + "/api.php?i="+_userID+"&l="+inLyrics+"";
var urlRequest:URLRequest = new URLRequest(url);
_UrlLoader = new URLLoader();
_UrlLoader.addEventListener(Event.COMPLETE, onSearchLyricsComplete,false,0,true);
_UrlLoader.addEventListener(IOErrorEvent.IO_ERROR, onSearchLyricsError,false,0,true);
_UrlLoader.load(urlRequest);
urlRequest = null;
}
/*
 Method: onSearchArtistAndTitleComplete
 Parameters:
 event : Event
Returns:  
 */

private function onSearchArtistAndTitleComplete(event : Event):void {
var resultXML:XML=XML(event.target.data);
validateResults(resultXML);
_UrlLoader.removeEventListener(Event.COMPLETE, onSearchArtistAndTitleComplete);
_UrlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onSearchArtistAndTitleError);
_UrlLoader = null;
}
/*
Method: onSearchArtistAndTitleError
Parameters:
event : IOErrorEvent
Returns:  
 */

private function onSearchArtistAndTitleError(event : IOErrorEvent):void {
dispatchEvent(new LyricsFlyEvent(LyricsFlyEvent.DATA_ERROR,event));
_UrlLoader = null;
}
/*
Method: onSearchLyricsComplete
Parameters:
event : Event
Returns:  
*/

private function onSearchLyricsComplete(event : Event):void {
var resultXML:XML=XML(event.target.data);
validateResults(resultXML);
_UrlLoader.removeEventListener(Event.COMPLETE, onSearchArtistAndTitleComplete);
_UrlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onSearchArtistAndTitleError);
_UrlLoader = null;
}
/*
Method: onSearchArtistAndTitleError
Parameters:
event : IOErrorEvent
Returns:  
 */

private function onSearchLyricsError(event : IOErrorEvent):void {
dispatchEvent(new LyricsFlyEvent(LyricsFlyEvent.DATA_ERROR,event));
_UrlLoader = null;
}
/*
Method: validateResults
Parameters:
event : inXML:XML
Returns:  
 */

private function validateResults(inXML:XML):void
{
var resultXML:XML = inXML;
var status:String = _parser.isValid(resultXML);
var lyricsObject:Object;
var evtResult:String;
switch (status) {
case "200" :
lyricsObject = _parser.parseLyricData(resultXML);
lyricsObject.result = true;
dispatchEvent(new LyricsFlyEvent(LyricsFlyEvent.DATA_LOADED,lyricsObject));
break;
case "204" :
//trace("NO CONTENT - Query returned no results");
lyricsObject = new Object()
lyricsObject.result = false;
lyricsObject.errorCode = "204";
lyricsObject.msg= "Query returned no results";
dispatchEvent(new LyricsFlyEvent(LyricsFlyEvent.DATA_ERROR,lyricsObject));
break;
case "300" :
//trace("TESTING LIMITED - Temporary access");
lyricsObject= _parser.parseLyricData(resultXML);
lyricsObject.result = true;
dispatchEvent(new LyricsFlyEvent(LyricsFlyEvent.DATA_LOADED,lyricsObject));
break;
case "400" :
//trace("MISSING KEY - Authorization failed!");
lyricsObject = new Object()
lyricsObject.result = false;
lyricsObject.errorCode="400"
lyricsObject.msg = "Authorization failed";
dispatchEvent(new LyricsFlyEvent(LyricsFlyEvent.DATA_ERROR,lyricsObject));
break;
case "401" :
//trace("UNAUTHORIZED - Authorization failed!");
lyricsObject = new Object()
lyricsObject.result = false;
lyricsObject.errorCode = "401";
lyricsObject.msg = "Authorization failed";
dispatchEvent(new LyricsFlyEvent(LyricsFlyEvent.DATA_ERROR,lyricsObject));
break;
case "402" :
//trace("LIMITED TIME - Limit query requests!");
lyricsObject = new Object()
lyricsObject.result = false;
lyricsObject.errorCode = "402";
lyricsObject.msg = "Limit query requests";
dispatchEvent(new LyricsFlyEvent(LyricsFlyEvent.DATA_ERROR,lyricsObject));
break;
case "406" :
//trace("QUERY TOO SHORT - Invalid query requests!");
lyricsObject = new Object()
lyricsObject.result = false;
lyricsObject.errorCode = "406";
lyricsObject.msg = "Invalid query requests";
dispatchEvent(new LyricsFlyEvent(LyricsFlyEvent.DATA_ERROR,lyricsObject));
break;
}
}
}
}
internal class SingletonEnforcer {
}

Here’s the custom events class for the Lyricsfly services class:

LyricsFlyEvent.as
/****************************
* Manuel Gonzalez           *
* design@stheory.com        *
* www.stheory.com           *
* www.codingcolor.com       *
*****************************/

package com.stheory.app.business{
import flash.events.Event;
public class LyricsFlyEvent extends Event
{
public var data:*;
public static var DATA_LOADED: String = "DATA_LOADED";
public static var DATA_ERROR: String = "DATA_ERROR";
public function LyricsFlyEvent(type:String, data:*)
{
this.data= data;
super(type);
}
}
}

Here’s the class that parses the xml from the Lyricsfly results:
LyricsDataParser.as
/********************************
*     Manuel Gonzalez           *
*     design@stheory.com        *
*   http://www.stheory.com      *
*   http://www.codingcolor.com  *
*********************************/

package com.stheory.app.business
{
import flash.xml.XMLDocument;
import flash.xml.XMLNode;
import com.adobe.utils.*;
public class LyricsDataParser
{
public function LyricsDataParser(){}
public function isValid(xml:XML):String
{
var resultstatus:String
var result:XMLDocument = new XMLDocument();
var dataObj:Object = new Object();
result.parseXML( xml.toXMLString() );
var nodes:Array = result.firstChild.childNodes;
for each( var node:XMLNode in nodes ) {
  switch( node.nodeName )
{
  case "status":
   resultstatus = node.firstChild.nodeValue;
   break;
}
   }
 return resultstatus;
}
/*
Method: parseLyricData
Parameters:
xml:XML
Returns:Object
*/
 
public function parseLyricData(xml:XML):Object
{
 var result:XMLDocument     = new XMLDocument();
 var dataObj:Object         = new Object();
 result.parseXML( xml.toXMLString() );
 var nodes:Array = result.firstChild.childNodes;
    for each( var node:XMLNode in nodes ) {
    switch( node.nodeName )
         {
                   case "sg":
                       dataObj = createDataObject(node);
                       break;
               }
           }
       
           return dataObj;
       }
/*
Method: createDataObject
Parameters:
inData:XMLNode
Returns:  Object
*/
 
private function createDataObject(inData:XMLNode):Object
{
//trace(" parseDataEntries xml " + inData)
var nodeObj = new Object;
var nodes:Array     = inData.childNodes;
 for each( var node:XMLNode in nodes )
 {
    switch( node.nodeName )
    {
       case "ar":
    //trace(node.firstChild.nodeValue)
     nodeObj.artist = node.firstChild.nodeValue;
         break;
    case "tt":
    //trace(node.firstChild.nodeValue);
    nodeObj.title = node.firstChild.nodeValue;
    break;
    case "al":
    //trace(node.firstChild.nodeValue);
    nodeObj.album = node.firstChild.nodeValue;
    break;
    case "tx":
    //trace(node.firstChild.nodeValue);
    var cleanStr:String = StringUtil.replace(node.firstChild.nodeValue, "[br]", "\n");
         nodeObj.lyrics = cleanStr;
    break;
    }
}
 return nodeObj;
       }
   }
}

So this is part one of a two part App. I already have the YouTube component successfully loading video, I’m going to add the ability to search, and control the video. I’ll be posting the test app shortly.

Download Source:
AS3 Lyricsfly App (130)

Be Sociable, Share!
Coding Color | 2009 All Rights Reserved.