Metalify - A Spotify Clone

Sun 05 September 2010, tagged: C++ Qt

For a few months I’ve wanted to make a clone of Spotify but I haven’t been able to justify the time. Last week I decided to take a week off and give it a go. The justification for this is that I wanted to deploy a Django website and learn Qt. I named my project Metalify.

Metalify Icon

Server Side

Most of the content that you see in the spotify client is actually presented via webpages. So in Metalify I deployed a Django server to server album listings, individual albums with thier track listing and a little blurb. I didn’t get a chance to implement the search features that Spotify has but tbh they are just more webpages.

Client Side

I had a hunch that Spotify was written in Qt (statically linked). I’ve used many many frameworks for apps (MFC, WxWidgets, Fox, Cocoa) but never Qt. Qt suits my needs as I wanted a single code base that compiles for MacOS X and Windows but looks native.

Metalify OSX Metalify Windows

As you can see they look pretty native on each platform.

Having never used Qt before I was surprised at how easy it was to get stuck in. Qt comes with a Qt Designer an interface builder application allowing you to build an interface and test it within the designer. Despite a few oddities it’s pretty easy to use and saves out a .UI file that you can load into you mainwindow class.

QtDesigner

Within Qt Designer you can edit style sheets to style your interface. Use

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
*{ 
    color: rgb(231, 231, 231);
}

QFrame{ 
    border: none; 
    background-color: #939393; 
}

QPushButton{ 
    color: rgb(10, 10, 10); 
}

This is a really good idea. I actually LOVE this feature. It makes so much sense. The downside is that it carries all the baggage of CSS, your CSS file can quickly become a complete mess!

The CSS allowed me to style the play/skip buttons and make them round

CSS Buttons

Use

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#playButton{ 
    width: 32px; 
    height: 32px; 
    background: url(:/resources/play.png); 
    border-radius: 32px; 
}

#playButton:hover{ 
    background: url(:/resources/play_hover.png);
}

#playButton:pressed{ 
    background: url(:/resources/play_depressed.png); 
}

By setting the border-radius for a button you can make it round, change for mouse over and being clicked. I’ve never made a cool looking button in a application framework this easily before.

The downside of the style sheet system is that not all widgets support it. This meant that I couldn’t style the volume (Phonon::VolumeSlider) or scrub widget (Phonon::SeekSlider). As far as I could work out the only way to customize the look of these widets was to subclass and override Widget::paint which seemed like overkill for a week long experiment.

Audio

Qt comes with a audio framework called Phonon. It’s not very mature but is workable. Combined with the network code it actually made playing MP3s on the mac VERY easy. Infact, taking some sample code I was downloading and playing MP3s within an hour.

Spotify has a caching mechanism so you can play tunes offline (if you pay) and to limit their bandwidth costs. I implemented a simple cache system which stored the downloaded MP3s in a platform specific cache folder. The logic is thus: If the file isn’t in the cache start playing the file streamed from the server (so it starts to play within a few seconds) and also download it from the server. This isn’t ideal as the file is downloaded twice. It was however quick to implement and allowed me to use the high level Qt classes.

On Win32 audio playback wasn’t so easy. For some reason certain MP3s just didn’t playback on Win32. Phonon on Win32 wraps DirectShow and DirectShow wraps various decoders. Despite the Qt Capabilities program saying it should be able to decode MP3 it kept failing for specific MP3s. I re-encoded them using Lame (default settings) and then they were able to play. This unnerved me a little and was my first hint that maybe Phonon isn’t as mature as I had hoped.

Integration of the Website and Client

As I said before Spotify displays most of it’s information via webpages. Similarly the right hand pane of Metalify is a QWebView. Integrating the website and the client was really easy.

1
2
3
webView->page()->mainFrame()->evaluateJavaScript(jQuery); 
... 
webView->page()->mainFrame()->addToJavaScriptWindowObject("player", player);

evaluateJavaScript allows me to inject any javascript I wished into the HTML page downloaded from the server. addToJavaScriptWindowObject allowed me to expose a carefully crafted C++ object to the Javascript running in the page.

Using these two access routes I was able to work out the currently selected track, see what tracks to start downloading and update the currently playing track. This was all a lot easier than I had expected.

Cross Platform Dev

The primary reason for choosing Qt was cross platform development. Qt actually helped (as opposed to a lot of tools that claim to help but actually get in the way). I read a few pages of the extensive Qt docs and I was away.

Qt uses a .pro file which is a make file for it’s qmake utility. I used qmake to create an XCode project and then XCode to compile/debug. Moving to WinXP I used QTCreator as an IDE which builds using Ming-w32 (a windows port of gcc). QTCreator is a fairly complete IDE that comes with QT and was fine for my needs. I don’t know if I could use it day in day out as I basically used it to call qmake! I could have chosen to build with Visual Studio but since my main aim was to get the code and build (not write or debug) on Windows it didn’t make sense to complicate the build process too much on Win32. Most of the time Qt hid the differences between Win32 and OS X from me and everything just worked. Very impressive.

Client Installer

As I was making a fake consumer application I needed to make an installer. As I had used Nullsoft Installer System (NSIS) before I quickly created an install/uninstall script and found myself on the usual DLL nightmare of Win32. Qt recommend the use ofDependency Walker which analyses an exe and lists all the DLLs required to run it. Turns out I needed a lot of them:

1
2
3
4
5
6
7
8
9
File "c:\Qt\2010.04\mingw\bin\mingwm10.dll"
File "c:\Qt\2010.04\mingw\bin\libgcc_s_dw2-1.dll"
File "c:\Qt\2010.04\qt\bin\phonon4.dll"
File "c:\Qt\2010.04\qt\bin\QtCore4.dll"
File "c:\Qt\2010.04\qt\bin\QtGui4.dll"
File "c:\Qt\2010.04\qt\bin\QtNetwork4.dll"
File "c:\Qt\2010.04\qt\bin\QtWebKit4.dll"
File "c:\Qt\2010.04\qt\bin\QtXmlPatterns4.dll"
This pushed the installer size to ~10MB.

Making an installer on MacOS was a little bit easier. Qt ships with a tool to look at an app and build a DMG (Disk Image) which the end user can mount and then copy the .App to their Applications folder. The one App contains all the Shared Libraries (DLLs) that the App needs to run.

1
macdeployqt client.app -dmg -no-plugins

To my horror this made an app that was 96MB and the DMG file was 32 MB. To be fair this is a Universal app so the App and Shared libraries were in x86 and PPC formats but that still over twice the size of the Win32 installer!

Lessons Learned

I really enjoyed writing working on this project and I think I learned and achieved a lot.

Qt REALLY is a great library. The fact that it comes under the free LGPL license or a proprietary license is very generous of Nokia. While I’d rather not see the GPL, I favour more liberal BSD/MIT licenses, I do understand that they want any good changes to Qt to come back to them. After all they are giving you the source to a VERY good library to play with so it’s good to give back.

If Spotify is built using Qt then they must be statically linking with QT. My app is massive compared to theirs. IF they are not useing Qt then I’d love to know why not as it really is ideally suited to this kind of task. It’s easily skinnable, robust, complete and well documented.

Django is very simple to use if a little tedious to set up and deploy behind apache. It’s not that different from TurboGears. It’s well documented and heavily indexed by google. Django Snippets is a great resource for niggly questions like “how to I efficiently sever a multi MB file”.

Qt’s community seems to be a bit lacking. I found few useful resources of creating custom Widgets. This seemed a shame to me.

Issues

The code is most definitely alpha quality. Firstly the download is far too big. I’m pretty sure static linking Qt into the client would make it bigger but not needing the Qt DLLs/Shared Libraries would be a win.

On Mac OSX the client takes 100% CPU time while playing an MP3. I only noticed this after finishing the code (I’ve got 4 CPUs so I didn’t notice loosing one of them). I’m pretty sure this is a Phonon bug/issue as All I do is ask Phonon to play the MP3 and then wait for events to fire.

The code to cache MP3s doesn’t condsider the size of the cache so it will grow for ever. Spotify sets a limit to downloaded content defaulting to 10GB.

If you play an album and then navigate to another album the player get confused. This would be trivial to fix but I’ve left it as an exercise for the reader ;)

The left hand pane of the client has links to What’s New, Radio etc these are all placeholders and do nothing. They would be easy to hook up to urls in the web view though.

The test website has three albums on it (quite a lot less than spotify but it’s close). I haven’t got permission to distribute these releases but they are all freely available from the bands for free as downloads. Please remember this is just sample code and a sample website.

Conclusion

I REALLY enjoyed this project. Cloning someone elses work is a great way to focus on the code and it’s easy to make progress and judge the quality of what you create. I think what is there is a good jumping off point for creating a competing product to spotify.

If you’re interested in taking the code and running with it please do. Execution Unit Ltd is placing this code into the public domain.

Source Code

You can get the source code for the project on BitBucket.