Why shouldn't your music player be as individual as your musical taste? We want to make adding new features to Songbird almost as easy as adding items to your playlist. To that end, we're using the same extension mechanism as Mozilla-based browsers like Firefox. If you are already familiar with XUL and JavaScript you will be extending Songbird in no time.
But why stop there? Because Songbird is a media player and not a web browser, we aren't going to make any assumptions about the type of window you are extending. Unlike Firefox, we allow you to overlay into any window type. Instead of allowing a single main XUL, as with Firefox, we encourage you to create lots of different XUL files and to present whatever media player interface you'd like. For example, we provide two: a mini-player and a standard player.
Building a simple extension to add functionality is fairly easy. In this simple tutorial, we'll show you how you can create a Songbird extension that overlays custom user interface elements into the Songbird UI.
Songbird makes use of the same extension mechanism as Firefox.
The source code for this tutorial may be browsed at the Songbird Source Browser
And the latest version of the extension is available here: Play/Pause/Stop Add-On
The Songbird UI is built with XUL(an acronym for "XML User Interface Language") which is pronounced "zool" and is similar in many respects to XHTML. XUL was originally created to make it easier to design and extend the UI for the Mozilla browser and it serves the same purpose in Songbird.
XUL can be used to create most modern UI layouts, including input controls such as dialog boxes, toolbars, menubars, and more (including the Songbird miniplayer and mainplayer).
The set of Songbird UI elements (e.g., menus, dialog boxes) are referred to as the Songbird chrome. The UI elements are bundled into a chrome package and are referenced using the chrome URI:
chrome://songbird-pauseplaystop/content/overlayMain.xul
The chrome URI may be broken down into the following components:
chromesongbird-pauseplaystopcontentoverlayMain.xulThe package names are defined in the chrome.manifest file.
Perhaps the most basic requirement for a media player is to provide shuttle controls so that the user can easily control the operation of the player. Certain controls have become standard, such as pause, play, and stop but there are many other possibilities. Developers may want to add rewind, fast-forward, or scrubbing controls; or simply rearrange their order or position on-screen.
The "Pause/Play/Stop" extension is implemented as a XUL overlay. Overlays allow an extension to add and/or modify UI elements as well as inject arbitrary script to be run in the context of the layout being overlaid. Overlays are the primary method by which an extension developer implements additional functionality.
Many media players allow users to customize their appearance through the use of skins or themes. At Songbird, we refer to these skins as feathers. Because Songbird is intended to load many different feathers, with many different types of layouts and id attributes, the standard Mozilla overlay style is not sufficient. Therefore, Songbird supports overlaying entire categories of layouts by windowtype, as well as overlaying directly inside of the custom Songbird elements that are used to create a Feathers layout. In this way, an extension programmer can say they want to target the Servicepane or the Library and be sure that the extension's content will appear in the Servicepane and Library no matter what Feathers may have been loaded.
More information on this topic can be found in the section discussing the chrome.manifest file.
Tools menu under "Create New Extension". The Extension Wizard walks you through the initial setup of the files and framework needed to get your basic extension off the ground, such as populating your chrome.manifest, install.rdf, and all the nitty gritty details (as covered below in the rest of this guide). Additionally, it helps create framework for extensions to use Songbird features such as Custom Views/Media Pages, Display Panes, Toolbar Buttons, Preferences, etc. This sample extension defines five files laid out as follows:
chrome.manifest chrome/content/overlayMenu.xul chrome/content/overlayMain.xul chrome/content/overlayMain.js install.rdf
Below, we'll cover each file in order.
The chrome.manifest file (Mozilla - chrome.manifest) contains instructions that specify user interface elements for the extension. It provides the locations of files and what they're used for. We'll first present the full file contents here, and then walk through line by line and explain in detail each command/line.
# Define our content namespace -- The folder MUST end with a / content songbird-pauseplaystop chrome/content/ # Overlay XUL into ALL menubars overlay chrome://songbird/content/xul/menuOverlay.xul chrome://songbird-pauseplaystop/content/overlayMenu.xul # Overlay XUL into ALL windows of type Songbird:Main that use the target XBL overlay windowtype:Songbird:Main chrome://songbird-pauseplaystop/content/overlayMain.xul
The first thing you need to do in your manifest file is register your content folder with the system, so you can specify your overlay files (note that you must include the trailing slash):
content songbird-pauseplaystop chrome/content/
This creates a content namespace named songbird-playpausestop and we tell it to look in the folder found at chrome/content/ in relationship to the manifest file. Then any file in that folder (or below it in the hierarchy) is available from the root URI chrome://songbird-pauseplaystop/content/
Other commands like skin or locale would create chrome://songbird-pauseplaystop/skin/, etc:
skin songbird-pauseplaystop classic/1.0 chrome/skin/
We will cover the skin and locale options in the Getting Started with Feathers guide.
In the following examples, you can see how we use the
chrome://songbird-pauseplaystop/content/ root URI to access all of the files in the chrome/content/ folder.
We want to overlay our extension's file chrome://songbird-pauseplaystop/cont...verlayMenu.xul into all menus in the application. This is achieved by targeting the special .xul target file for menu items, chrome://songbird/content/xul/menuOverlay.xul
overlay chrome://songbird/content/xul/menuOverlay.xul chrome://songbird-pauseplaystop/content/overlayMenu.xul
We also want to overlay the file
chrome://songbird-pauseplaystop/cont...verlayMain.xul into all Songbird feathers. We do this by targeting all windows of type Songbird:Main
overlay windowtype:Songbird:Main chrome://songbird-pauseplaystop/content/overlayMain.xul
The first thing you'll want to to do is put up an extra menu item in the tools menu, so the user may select it and execute your code. Again, we'll present the contents of the file in its entirety, and then explain in detail each line following.
<?xml version="1.0"?>
<!DOCTYPE window SYSTEM "chrome://songbird/locale/songbird.dtd">
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="songbird_menu_overlay"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<!--
This file is enabled by the chrome.manifest line:
overlay chrome://songbird/content/xul/menuOverlay.xul chrome://songbird-pauseplaystop/content/overlayMenu.xul
And will be applied to __ALL__ properly implemented menubar instances.
You can browse the menuOverlay file here:
http://publicsvn.songbirdnest.com/browser/trunk/app/content/xul/menuOverlay.xul
-->
<!-- Into the "Tools" menu, before the Prefs Separator -->
<menupopup id="menu_ToolsPopup">
<menuitem
label="!!! OVERLAY MENUITEM !!!"
insertbefore="menu_PrefsSeparator"
oncommand="onMyOverlayMenuitem()"/>
</menupopup>
<!-- You can load a script file, or just put script inline like this. -->
<script>
<![CDATA[
function onMyOverlayMenuitem() {
alert('Woo Hoo, I made a menuitem!');
}
]]>
</script>
</overlay>
You must start your file, like all Mozilla XML files, with <?xml> and you can load our .dtd (or your own, or however many you like) with the DOCTYPE identifier:
<?xml version="1.0"?> <!DOCTYPE window SYSTEM "chrome://songbird/locale/songbird.dtd" >
You must have an element of type <overlay> as your root element. The XML namespaces may sometimes be necessary to target different types of elements in your declarations, later. It is best to preserve them.
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="songbird_menu_overlay"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
The only important part of the direct children of the <overlay> element is the id attribute. The overlay system will search the parent document for an element with that id attribute. However, I could have written <anythingIwant id="menu_ToolsPopup"> and everything will still work.
<menupopup id="menu_ToolsPopup">
<menuitem
label="!!! OVERLAY MENUITEM !!!"
insertbefore="menu_PrefsSeparator"
oncommand="onMyOverlayMenuitem()"/>
</menupopup>
Once an element is properly targeted for overlay, all other attributes on the overlaying element will be applied to the overlay target. In addition, all of the children of the overlaying element will be added to the overlay target as children. If a child already exists with the same id attribute, only the attributes will be applied to the existing child.
In this case, we are adding a <menuitem> element to the Songbird Tools menu. In order to know what id attributes exist to be overlay targets, you must examine the implementation of the target you are overlaying. For menus, that is
chrome://songbird/content/xul/menuOverlay.xul and can be found online with our source browser, here.
Specifically, on line 112, you can see where the Tools menupopup has id="menu_ToolsPopup"
In order to properly position your children into their overlay target, you may add the special attribute insertbefore with the value of an id in the target document for your item to be placed before.
Without the insertbefore attribute, your elements will be positioned after the other children already within the overlay target. You can see the <menuseparator id="menu_PrefsSeparator"/> element that we place
our <menuitem> before, here
Just like in HTML, you may place JavaScript inline with the declaration of a <script> element.
<script>
<![CDATA[
function onMyOverlayMenuitem() {
alert('Woo Hoo, I made a menuitem!');
}
]]>
</script>
This code simply defines a function that will be called from the oncommand attribute when the user selects our spiffy new <menuitem>.
We have tried hard to ensure that wherever possible the id attributes used in our menus are identical to the id attributes used in the Firefox menus.
This should facilitate porting Firefox extensions to Songbird with a minimum of effort (though that topic is beyond the scope of this document).
Next, it's important to know how to add your own elements into all the custom elements provided by Songbird.
<?xml version="1.0"?>
<!DOCTYPE window SYSTEM "chrome://songbird/locale/songbird.dtd" >
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="songbird_main_overlay"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<!--
This file is enabled by the chrome.manifest line:
overlay windowtype:Songbird:Main chrome://songbird-pauseplaystop/content/overlayMain.xul
And will be applied to __ALL__ properly implemented windows of windowtype="Songbird:Main"
-->
<!-- Import your JavaScript code. -->
<script type="application/x-javascript"
src="chrome://songbird-pauseplaystop/content/overlayMain.js"/>
<!--
This overlay will land __INSIDE__ of the <sb-player-control-buttons/> element,
no matter what .xul layout is used to show the control buttons element.
-->
<xul:hbox id="sb-player-control-buttons">
<xul:sb-player-pause-button
id="sb-player-control-buttons-pause"
insertbefore="sb-player-control-buttons-playpause"
popupanchor="topleft" popupalign="bottomleft"/>
<xul:sb-player-play-button
id="sb-player-control-buttons-play"
insertbefore="sb-player-control-buttons-playpause"
popupanchor="topleft" popupalign="bottomleft"/>
<xul:sb-player-stop-button
id="sb-player-control-buttons-stop"
insertbefore="sb-player-control-buttons-playpause"
popupanchor="topleft" popupalign="bottomleft"/>
<xul:sb-player-playpause-button
id="sb-player-control-buttons-playpause" popupanchor="topleft"
popupalign="bottomleft" hidden="true"/>
</xul:hbox>
<xul:hbox id="sb-mini-player-controls">
<xul:sb-player-pause-button
id="mini_btn_pause" class="miniplayer"
insertbefore="sb-mini-player-controls-playpause" popupanchor="topleft"
popupalign="bottomleft"/>
<xul:sb-player-play-button
id="mini_btn_play" class="miniplayer"
insertbefore="sb-mini-player-controls-playpause" popupanchor="topleft"
popupalign="bottomleft"/>
<xul:sb-player-stop-button
id="mini_btn_stop" class="miniplayer"
insertbefore="sb-mini-player-controls-playpause" popupanchor="topleft"
popupalign="bottomleft"/>
<xul:sb-player-playpause-button
id="sb-mini-player-controls-playpause" popupanchor="topleft"
popupalign="bottomleft" hidden="true"/>
</xul:hbox>
<!--
More overlays inside of XBL elements.
Here we're sticking extra strings in the metadata labels.
-->
<xul:hbox id="sb-player-artist-label">
<xul:label value="&metadata.artist; - "
insertbefore="sb-player-artist-label-label"
class="faceplate-text"/>
</xul:hbox>
<xul:hbox id="sb-player-album-label">
<xul:label value="&metadata.album; - "
insertbefore="sb-player-album-label-label"
class="faceplate-text"/>
</xul:hbox>
<xul:hbox id="sb-player-title-label">
<xul:label value="&metadata.title; - "
insertbefore="sb-player-title-label-label"
class="faceplate-text"/>
</xul:hbox>
<xul:hbox id="sb-player-numplaylistitems-label">
<xul:label xvalue="Items? "
insertbefore="sb-player-numplaylistitems-label-label"/>
</xul:hbox>
</overlay>
Just like in HTML, you may load external script files in addition to simple inline scripts.
<script type="application/x-javascript"
src="chrome://songbird-pauseplaystop/content/overlayMain.js"/>
The overlayMain.js file is documented below.
Here, we're targeting any Feathers layout that contains
<sb-player-control-buttons> because the definition of that element contains a <xul:hbox id="sb-player-control-buttons"> -- in general, any of our custom elements will contain an inner element with an id of the same name as the custom element.
We can achieve our goal of replacing the play/pause button with three separate play/pause/stop buttons by hiding the play/pause button.
<xul:hbox id="sb-player-control-buttons">
<xul:sb-player-pause-button
id="sb-player-control-buttons-pause"
insertbefore="sb-player-control-buttons-playpause"
popupanchor="topleft" popupalign="bottomleft"/>
<xul:sb-player-play-button
id="sb-player-control-buttons-play"
insertbefore="sb-player-control-buttons-playpause"
popupanchor="topleft" popupalign="bottomleft"/>
<xul:sb-player-stop-button
id="sb-player-control-buttons-stop"
insertbefore="sb-player-control-buttons-playpause"
popupanchor="topleft" popupalign="bottomleft"/>
<xul:sb-player-playpause-button
id="sb-player-control-buttons-playpause" popupanchor="topleft"
popupalign="bottomleft" hidden="true"/>
</xul:hbox>
You can see the definition of the <sb-player-control-buttons> element, here. Note that the <xul:sb-player-playpause-button> already existed, and we are just adding the hidden="true" attribute to it.
To know what id attributes exist that may be overlay targets in the windowtype:Songbird:Main, you should look at our XBL definitions.
Complex extension functionality is implemented in JavaScript. The overlayMain.js file is loaded by the overlayMain.xul overlay file as shown earlier.
// Do some slick handling of JS functionality. Set event handlers, etc.
// tag your function names with your extension's name to avoid namespace collisions!!!
// Want to know if someone asked an item in a playlist to be played?
function PausePlayStop_onPlaylistPlay( aEvent ) {
var playlist = aEvent.originalTarget;
var number = "unknown";
try {
// Events from the "inner" playlist will be wrapped
while ( playlist.wrappedJSObject )
playlist = playlist.wrappedJSObject;
// Once we have a "real" playlist, we can ask for things like length
number = playlist.mediaListView.length;
} catch(e) { alert( "ERROR: " + e ) }
// And then do something with that info, here.
alert( playlist.nodeName + " has " + number + " item(s)" );
}
// Unload our listeners!
function PausePlayStop_onUnload( aEvent ) {
window.removeEventListener( "unload", PausePlayStop_onUnload, false );
document.removeEventListener( "playlist-play", PausePlayStop_onPlaylistPlay, true );
}
// ALWAYS listen for the window.unload event to be able to remove our listeners.
window.addEventListener( "unload", PausePlayStop_onUnload, false );
// Listen for the "playlist-play" event to know when a user has started playback from the playlist.
document.addEventListener( "playlist-play", PausePlayStop_onPlaylistPlay, true );
This code makes use of XPCOM components provided by Songbird, and is driven by standard DOM manipulations (addEventListener(), etc) like you would use in HTML.
In this script, we're watching for the playlist-play event to be fired in the system and doing something randomly interesting with the playlist that launched it.
You can find the documentation for our component interfaces, here
Installation information for the "Play/Pause/Stop" extension is provided in the install.rdf file.
<?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#">
<Description rdf:about="urn:mozilla:install-manifest">
<em:id>pauseplaystop@songbirdnest.com</em:id>
<em:name>Pause/Play/Stop Buttons</em:name>
<em:version>0.0.10</em:version>
<em:creator>Pioneers of the Inevitable</em:creator>
<em:description>Changes the single Play button in Songbird to separate Play/Pause/Stop buttons</em:description>
<em:homepageURL>http://addons.songbirdnest.com/extensions/detail/48</em:homepageURL>
<em:targetApplication>
<Description>
<em:id>songbird@songbirdnest.com</em:id>
<em:minVersion>0.3pre</em:minVersion>
<em:maxVersion>0.3.*</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>
Each extension must have a unique identifier specified as an <em:id> element. In this example, the ID is pauseplaystop@songbirdnest.com. The ID may also be a GUID in the form of {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
(see Generating GUIDs).
NOTE: MAKE SURE TO CHANGE THE ID TO A UNIQUE VALUE FOR YOUR EXTENSION! IF YOU USE A GUID, YOU MUST PUT THE BRACES {} AROUND IT!
Extensions also have versions specified as an <em:version> element, 0.0.10 in this example. Every time you create a new version of your extension, you should increment this value. This allows the Songbird Add-Ons website to automatically provide updates to users who have a prior version of your extension installed.
Extensions also must specify what applications they extend using an <em:targetApplication> element. In this example, the target application is Songbird, specified using its ID songbird@songbirdnest.com. The minimum and maximum versions are also specified as 0.3pre and 0.3+ respectively.
When the extension is displayed in the Songbird extension manager, the displayed extension name is taken from the <em:name> element. A description taken from the <em:description> element is also displayed.
The information from the install.rdf file is also used by the Add-Ons website to prepopulate the information on an extension's download page.
Extensions are packaged as XPI files. XPI files are created by packaging the extension files as a ZIP file with a file extension of xpi. The following is an example of how to package up the XPI file on a Un*x system
$ cd pauseplaystop/src $ zip -r ../pauseplaystop.xpi . adding: chrome/ (stored 0%) adding: chrome/content/ (stored 0%) adding: chrome/content/overlayMenu.xul (deflated 58%) adding: chrome/content/overlayMain.xul (deflated 62%) adding: chrome/content/overlayMain.js(deflated 60%) adding: chrome.manifest (deflated 58%) adding: install.rdf (deflated 59%) $
On a Microsoft Windows™ operating system, you can use a compression tool like WinZip to create a .ZIP archive, and then simply rename the file so its file extension is .xpi instead of .zip
To install the extension, launch Songbird and select the
Tools>Add-Ons... menu. Click the Install... button and navigate to and select the pauseplaystop.xpi file. Alternatively, you can just drag and drop the pauseplaystop.xpi file into the Songbird browser to install it.
Relaunch Songbird, and you should see a !!! OVERLAY MENUITEM !!! menu item in the Tools menu. Select it and you will see the alert we created. Oh yes, and there should also be Pause/Play/Stop buttons in the mainwin.
Since Songbird extension packages are simply zipped files, you can look at other extensions as examples. See the Songbird extensions page for examples. You can also browse the Windjay extensions sources. You can also see the source for some of Songbird's extensions at the Songbird extensions source browser. While not fully compatible with Songbird, Firefox extensions should also provide useful examples.
Documentation on the various Songbird components may be found at the Songbird Developer's page. XUL documentation may be found at the Mozilla developer site.
To check out the Songbird development plans, visit the Songbird wiki.
Check out the Songbird SDK API Documentation if you need to find out more information about the APIs (such as properties, methods, etc.)
And lastly, please consider adding locale support to your extension and having it localised @ BabelZilla!
If you have questions, flip through the messages on the Songbird Developer Google Group, or post your own questions.
© 2005-2009 Pioneers of the Inevitable