mashTape Provider API

    Introduction

    The new mashTape 0.2.0 add-on allows developers to extend it by developing new providers to provide additional content sources for Songbird users.  While mashTape comes bundled with a set of default providers, it can be extended via simple JavaScript extensions.

    Examples

    If you're the kind of person who wants to look at some code while reading this guide, we've implemented two additional providers to show how to extend mashTape.

    Architecture

    The mashTape.idl interface file describes the types of providers possible.  In a nutshell there exist 4 classes of providers, one for each of the 4 tabs mashTape supports:

    • Artist Info (sbIMashTapeInfoProvider)
    • News (sbIMashTapeRSSProvider)
    • Photos (sbIMashTapePhotoProvider)
    • Videos (sbIMashTapeFlashProvider)

    To implement a mashTape extension, you simply need to provide an XPCOM component that implements one of these 4 interfaces.  All 4 of these interfaces are derived from sbIMashTapeProvider.

    sbIMashTapeProvider

    To be a mashTape provider, regardless of which specific sub-type you implement, your component will need to provide a minimum of two attributes (the provider name & type), and a query() method:

    readonly attribute string providerName;
    readonly attribute string providerType;
    void query(in AUTF8String searchTerms, in sbIMashTapeCallback updateFn);

    providerName

    providerName is simply a freeform string naming your provider; this is visible to the end-user in the mashTape preference pane so they can enable and disable individual services. 

    providerType

    providerType is a short text string noting which class of provider your provider falls into:

    • info (for Artist Info)
    • rss (for News)
    • photo (for Photos)
    • flash (for Videos)

    Your provider *must* have providerType set to one of those 4 strings, or else it will fail to be detected.

    query()

    The query method simply takes in two parameters, the first being the search terms.  mashTape will call the query() method passing in the artist name for the searchTerms.  The second parameter is a callback function within mashTape that takes care of the rendering of results.

    sbIMashTape{Photo|RSS|Flash}Provider

    These three types of providers, at the moment, simply extend sbIMashTapeProvider by having a providerIcon attribute which is simply a string pointing to a 16x16 favicon-style image for the provider.  Typically these should be bundled within the chrome of your add-on so that they loaded locally, but they can also point to remote sites (e.g. http://getsongbird.com/favicon.ico)

    sbIMashTapeInfoProvider

    The artist info provider extends sbIMashTapeProvider by having 5 different providerIcon fields:

    • providerIconBio
    • providerIconTags
    • providerIconDiscography
    • providerIconLinks
    • providerIconMembers

    These are the various providerIcon strings for the different sections an artist info provider can provide.  sbIMashTapeInfoProviders must also provide a numSections attribute which is an integer count of the # of sections the infoProvider provides.  e.g. if your provider implements all 5 sections, numSections should be 5.  If it only implements the Biography & Discography, numSections should be 2.

    Developing a mashTape Provider Add-on

    We'll now take a look at putting together an add-on, specifically we'll look at how the Del.icio.us add-on is put together.  If you grab the Del.icio.us XPI and unzip it, you'll see the following files:

    ./chrome/content/favicon.ico
    ./chrome/content/icon.png
    ./chrome/content/main.xul
    ./chrome.manifest
    ./components/Delicious.js
    ./install.rdf
    

    The favicon.ico is our providerIcon mentioned above, the 16x16 favicon from http://delicious.com/favicon.ico.  The icon.png is referenced from install.rdf as the add-on icon to display in the add-ons manager and on the add-ons site.  The convention is to simply overlay the 16x16 favicon on top of the mashTape "tape" icon.

    Since there isn't "chrome" in the traditional sense of a Mozilla add-on, the rest of the chrome sub-directory is empty, and the chrome.manifest reflects this by merely setting pointers to the paths:

    chrome.manifest

    content mt_delicious chrome/content/
    skin mt_delicious classic/1.0 chrome/skin/

    There is nothing special in the install.rdf file, it's a standard vanilla install.rdf.

    install.rdf

    <?xml version="1.0"?>
    <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:em="http://www.mozilla.org/2004/em-rdf#"
         xmlns:songbird="http://www.songbirdnest.com/2007/addon-metadata-rdf#">
        <Description rdf:about="urn:mozilla:install-manifest">
            <em:id>delicious@grommit.com</em:id>
            <em:name>Delicious mashTape Provider </em:name>
            <em:version>0.1</em:version>
            <em:creator>Stephen Lau (stevel@songbirdnest.com)</em:creator>
            <em:description>Adds a Delicious mashTape RSS/News provider</em:description>
            <em:iconURL>chrome://mt_delicious/content/icon.png</em:iconURL>
            <em:homepageURL>http://mashtape.mozdev.org</em:homepageURL>
            <em:targetApplication>
                <Description>
                    <em:id>songbird@songbirdnest.com</em:id>
                    <em:minVersion>0.8.0pre</em:minVersion>
                    <em:maxVersion>0.8.0pre</em:maxVersion>
                </Description>
            </em:targetApplication>
        </Description>
    </RDF>

    You can see the <em:iconURL> that points to the add-on icon in the chrome/content/icon.png, but otherwise this is a totally standard vanilla install.rdf file.

    This leaves us with only the provider XPCOM Javascript file.  You can name it whatever you like, it just needs to be in the components sub-directory so Songbird will detect it during its scan for XPCOM components during initialisation.

    Delicious.js

    Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    
    const Cc = Components.classes;
    const Ci = Components.interfaces;
    const Cr = Components.results;
    
    const DESCRIPTION = "mashTape Provider: Delicious";
    const CID         = "{6c780af0-8048-11dd-ad8b-0800200c9a66}";
    const CONTRACTID  = "@songbirdnest.com/mashTape/provider/rss/Delicious;1";
    
    // XPCOM constructor for our Delicious mashTape provider
    function Delicious() {
    	this.wrappedJSObject = this;
    }
    
    Delicious.prototype.constructor = Delicious;
    Delicious.prototype = {
    	classDescription: DESCRIPTION,
    	classID:          Components.ID(CID),
    	contractID:       CONTRACTID,
    	QueryInterface: XPCOMUtils.generateQI([Ci.sbIMashTapeRSSProvider,
    			Ci.sbIMashTapeProvider]),
    
    	providerName: "Delicious",
    	providerType: "rss",
    	providerUrl: "http://del.icio.us",
    	providerIcon: "chrome://mt_delicious/content/favicon.ico",
    
    	query: function(artist, callback) {
    		var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
    			.createInstance(Ci.nsIXMLHttpRequest);
    		var url = "http://feeds.delicious.com/v2/rss/tag/" + artist;
    		var name = this.providerName;
    		var providerurl = this.providerUrl;
    		req.open("GET", encodeURI(url), true);
    		req.onreadystatechange = function(ev) {
    			return function(updateFn, providerName, providerUrl) {
    				if (req.readyState != 4)
    					return;
    				if (req.status == 200) {
    					var results = new Array();
    					var x = new XML(req.responseText.replace(
    						/<\?xml version="1.0" encoding="[uU][tT][fF]-8"\s?\?>/,
    						""));
    					for each (var entry in x..item) {
    						var pubDate = entry.pubDate.toString();
    						pubDate = pubDate.replace(/\+0000$/, "GMT");
    						var timestamp = new Date(pubDate);
    
    						var item = {
    							title: entry.title,
    							url: entry.link,
    							time: timestamp.getTime(),
    							provider: providerName,
    							providerUrl: providerUrl,
    							content: entry.description,
    						}
    						results.push(item);
    					}
    					
    					results.wrappedJSObject = results;
    					updateFn.wrappedJSObject.update(CONTRACTID, results);
    				}
    			}(callback, name, providerurl);
    		}
    		req.send(null);
    	},
    }
    
    var components = [Delicious];
    function NSGetModule(compMgr, fileSpec) {
    	return XPCOMUtils.generateModule([Delicious]);
    }

    You can see this is a pretty simple component.  It simply creates our Delicious object, giving it a description, classID, and contractID.  You MUST generate a unique contract ID for each provider.  The UUID Web Generator page is a great page to go to to get a guaranteed unique ID for your provider.

    QueryInterface: XPCOMUtils.generateQI([Ci.sbIMashTapeRSSProvider,
    			Ci.sbIMashTapeProvider]),

    This line uses the convenient XPCOMUtils methods (imported from XPCOMUtils.jsm on the first line of Delicious.js) to generate the appropriate QueryInterface lines setting our Delicious provider to implement both the sbIMashTapeRSSProvider & sbIMashTapeProvider interfaces.

    Being an sbIMashTapeProvider, it now needs to provide providerName & providerType, as well as query().  To implement sbIMashTapeRSSProvider it needs to provide providerIcon as well, you can see all of these implemented in the lines following QueryInterface:

     

    providerName: "Delicious",
    	providerType: "rss",
    	providerUrl: "http://del.icio.us",
    	providerIcon: "chrome://mt_delicious/content/favicon.ico",
    
    	query: function(artist, callback) {

    The providerUrl is not part of any interface, it's just a convenient shortcut used later on in query()

    Making the actual query()

    All a mashTape provider needs to do, generally, is make an outgoing asynchronous web call (using XMLHttpRequest) and then format the results back in such a way that mashTape can interpret them, and pass the results back to mashTape via the callback.  In the body of the query() method above you can see it sets up the XMLHttpRequest and issues it asynchronously.  Within the onreadystatechange handler, where it gets back the data, it uses E4X to parse the XML.  It then formats it into the format needed for a mashTape RSS Provider, and then issues a call to the callback function.  Generally, the results need to be an array.  In order to pass the array back via XPCOM, we need to do the following trick:

    results.wrappedJSObject = results;

    We then pass this to the update function callback via:

    updateFn.wrappedJSObject.update(CONTRACTID, results);

    The update() routine takes two parameters, the Contract ID of our provider (in order to match it up against which providers it made outgoing query() calls to), and the actual results array.

    Formatting the results

    The results need to be formatted differently depending on what class your provider is.

    Artist Info

    RSS/News

    results needs to be an array of elements, where each element is an object consisting of:

     Attribute
    Description
     title  The title of the entry
     url  The "Read More" detail link to the entry for further reading
     time  The timestamp (in EPOCH format, # of seconds since Jan 1, 1970)
     provider  The name of the provider (typically the same as .providerName)
     providerUrl  The URL to the provider's homepage
     content  The actual contents of the article to be read from within mashTape

     

    Photo

    results needs to be an array of elements, where each element is an object consisting of:

     Attribute
    Description
     title  The title of the photo
     url  The link for more detail on the photo
     small  The URL to the small version of the photo
     medium  The URL to the medium version of the photo
     large  The URL to the large version of the photo
     owner
     The photo author's name
     ownerUrl
     The link to the author's page for more info on the author
     time
     The timestamp (in EPOCH format, # of seconds since Jan 1, 1970)
     width
     The width of the small image
     height  The height of the small image

     

    Flash/Video

    results needs to be an array of elements, where each element is an object consisting of:

     Attribute
    Description
     title  The title of the entry
     url  The link to the page for more information on this video
     swfUrl  The direct URL to the swf (used as the src for the object embed)
     duration  The length in seconds of the video if available (set to 0 if not)
     time  The timestamp (in EPOCH format, # of seconds since Jan 1, 1970)
     thumbnail  The thumbnail image of the video
     author  The video author's name
     authorUrl  The link to the author's page for more info on the author
     description  The description of the video
     provider  The name of the provider
     providerUrl  The link to the provider's homepage
     ratio
     The ratio to attempt to keep the photo during resizing (width / height)
     width  The width of the video
     height  The height of the video

     

    Extra Credit: Checking for mashTape Presence

    For bonus points, you can add a few simple lines to check to make sure mashTape is installed in case a user installs your provider without having installed mashTape previously.  Simply add the following line to your chrome.manifest:

    overlay chrome://songbird/content/xul/layoutBaseOverlay.xul chrome://mt_delicious/content/main.xul
    

    Replacing "mt_delicious" with the path to your chrome content URL.  And then add the following chrome/content/main.xul file:

    <?xml version="1.0" encoding="UTF-8"?>
    <?xml-stylesheet href="chrome://shoutcast-radio/skin/sps.css" type="text/css"?>
    <overlay id="shoutcast-radio-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    	<script type="application/x-javascript">
    		window.addEventListener("load", function() {
    			if (!("sbIMashTapeProvider" in Components.interfaces)) {
    				var msg = "You installed the Delicious mashTape provider which requires the mashTape add-on to also be installed.  Do you want to install this add-on now?";
    				if (confirm(msg)) {
    					installXPI("http://addons.songbirdnest.com/addon/73/compatible-xpi");
    				}
    			}
    		}, false);
    	</script>
    </overlay>
    Tag page
    • No tags
    Viewing 4 of 4 comments: view all
    Various people all over the world get the <a href="http://goodfinance-blog.com">loans</a> from different creditors, just because it is comfortable.
    Posted 19:52, 9 Oct 2011
    Houses are expensive and not everybody can buy it. Nevertheless, personal loans are invented to support different people in such kind of hard situations.
    Posted 18:55, 11 Oct 2011
    Specialists claim that mortgage loans aid a lot of people to live the way they want, because they can feel free to buy needed goods. Furthermore, a lot of banks offer short term loan for different classes of people.
    Posted 22:06, 14 Oct 2011
    Set your life time more easy take the business loans and everything you want.
    Posted 00:56, 15 Oct 2011
    Viewing 4 of 4 comments: view all
    You must login to post a comment.
    Powered by MindTouch Core
    Real Time Web Analytics