Planet Banshee: from the great minds of our community

November 18, 2008

It takes time to make good stuff.

Sometimes longer than we'd like. Over two years ago at a Boston GNOME Summit Aaron Bockover laid out some of his ideas about the future of Banshee. At the time Banshee wasn't very mature, it was in need of some love and new blood. In the room along with us was Gabriel Burt, Miguel de Icaza and his band of mono people, Mike Urbanski, Brandon Hale, and a few others.

Over the course of this session we discussed things like the much-needed play queue, the need to redo a bunch of the guts, better device support, and a myriad of other ideas and thoughts. At the time it seemed so impossible that Banshee would ever get there.

This week Aaron announced Banshee 1.4, and in my view this is the Banshee that we envisioned. Much has changed. Since then Gabriel has been working on Banshee at Novell for a year now, and the Banshee community has grown by leaps and bounds.

2663158875_5d456e266a

In many ways I feel like Banshee 1.4 is what 1.0 should have been. Hindsight is very 20/20, but looking back at the amount of work accomplished by the team (on top of their other work duties) and the solid base that is current Banshee, I think the decision to rework the guts and push through to what we have today has been worth it. I think that getting out 1.0 and 1.2 when they did come out was important, even though some things weren't finished.

So now that we're here ... aaaaahhhhhhhh.

So now what? Well, I for one have been looking forward to this day for a number of reasons. One, I feel that Banshee has now reached a point where it can be boring. By "boring" I mean mature. The big churn is over and now we can concentrate on the sexy little bits.

I myself have been lucky to watch the Ubuntu part of the Banshee community grow. We have hyperair maintaining the Banshee team PPA, which provides Ubuntu users top-notch binaries for running Banshee. Prior to hyperair, there was no one really working on delivering fresh-Banshee to Ubuntu users, so my thanks go out to him. I've literally forgotten how to build Banshee from source. :)

And clearly no one can ever forget Sebastian Dröge's work on not just Banshee, but the entire Mono stack in Ubuntu over the years. He's been off doing awesome things for Collabora, but his contributions to Ubuntu, Debian, and Banshee are very significant. And lastly, my personal bug hero, Andrew Conkling, who has been that "bridge" between Ubuntu and upstream; triaging bugs and ensuring that the right bugs get reported to the Banshee developers and generally kicking serious butt.

I'd also like to thank those of you out there who have reported bugs in Banshee, helped people in IRC, and done general support and advocacy. You're all full of awesome.

November 17, 2008

A couple of ideas for contributing to Banshee

Jono's blog post about Banshee Kickin' it has some interesting comments that I'd like to address, specifically regarding the new track editor and Internet Radio in the recently released Banshee 1.4.

The Track Editor

The new track editor is fully extensible. This means you can add your own pages to the dialog and get full first class editing support. All of the current pages are implemented in the exact same way you would need to write an extension page.

What I would like to see are a few pages contributed at least initially by the community:

  • Creative Commons metadata/license support
    A lot of work on this in the past had been done in the past, but never to a quality or completeness I was comfortable with for including in core. This support could either be an extension page to the editor, or an extension that interacts with existing pages/fields, like the copyright field.

    Additionally, the extension could make a track column and query field available, so you could easily search for and view Creative Commons music. This was a pain to do in legacy Banshee, but it's a few lines of code in 1.x.

  • Music Brainz
    A page that allows for figuring out missing metadata, and also submitting track information back to MusicBrainz. Maybe it could also generate PUIDs. We have a complete C# implementation of the MusicBrainz web API (no native library needed) thanks to Scott Peterson, and it's used in other areas in Banshee. This really should not be that complicated to do.

I wrote a quick example today on how to extend the track editor. The example implements a History Page which allows you to change the play count, skip count, and last played date to arbitrary values. More could be done here, and in fact I'd like to make this a core page anyway.

Banshee History Extension to the Track Editor

  • Download the example
  • Extract, change directories, and make run
  • Play with the code!
  • You need Banshee 1.4 and Mono 1.9 or better (I opted to use C# 3.0 in the example)

Internet Radio

We made a decision early on in the 1.x series to provide very strong Last.FM support. We're fairly resource constrained, so this meant making traditional Internet Radio something of a second class citizen. In fact, I made a choice to not ship any default radio stations because really I should not be the one picking stations, and so many of them fall offline in the life cycle of a distribution iteration that supporting this is silly.

However, I made sure all the underlying pieces for good Internet Radio support were in place. We have a station editor, stream metadata is reflected in the UI so when playing a stream you can see "track changes" which in turn can trigger cover art, and the RadioTrackInfo object even handles fetching and parsing of remote station playlsits. We support M3U, PLS, ASX, and XSPF formats, and possibly some others I am forgetting.

The Radio source that ships by default is a bit bare in terms of UI, but this was intentional. You can add new stations and manage them, but we really need something like StreamTuner or Shoutcast, like Jono says, that builds on the Internet Radio core features. Implementing new sources is fairly straightforward. You can even extend the existing Radio source to build on it.

Implementing this would be a great standalone extension that I would love to ultimately roll back into core, maybe for 1.6 if an enthusiastic contributor steps up to the plate.

So what are you waiting for?

Join the community of enthusiastic Banshee contributors and write something great today. We have a large and functional API, lots of features to build on, and everything is designed around extensibility.

Such a large API can be daunting to learn, yes, but Banshee is also organized very well. We also have API documentation for Monodoc, and probably the best way to learn about Banshee's internals is to just read the source to the extensions we ship in core. All the great features in Banshee are just extensions!

Lastly, don't hesitate to stop by on the IRC channel asking for help!

Banshee 1.4 hits the streets, packed with Awesome

The Banshee logo

After three months of hard work on feature additions, a slew of bug fixes, stability and performance improvements, and a small tangent on porting to Mac OS X, we have released Banshee 1.4 -- the new stable series!

Get It!

HTC G1/Android Support Out-of-the-box

Banshee Device Overview

Got a G1? Get a Banshee! Banshee is the first media player to offer a customized experience for the Android/G1 phone.

G1 Purchased Music Source
  • Synchronize or manually manage your media collection on your G1 phone
  • Cover art is fully supported on the G1
  • Import music you purchased through the Amazon MP3 store on the G1 in one quick pass

Additionally, if you try to delete music you purchased on the Amazon MP3 store without actually being in the special "Purchased Music" source, Banshee will not comply. This prevents accidental deletion of music you may not yet have backed up to your desktop computer. To remove purchased music from the device, do so from the "Purchased Music" source.

With the G1 + Banshee, an experience similar to what iPhone users enjoy is available.

Amazon MP3 Store + G1 + Banshee

A final note on the G1 support: because the Android platform is open source, I was able to easily figure out optimal ways of implementing Android/G1 support. For instance, I was unsure what the maximum cover art size should be on the device, so I just read the source. It was a nice for once to not have to reverse engineer or guess!

Banshee on Mac OS X

I wrote a bit about this already, but 1.4 ushers in a new era for Banshee! From now on, Banshee will always be officially released and maintained for Mac OS X, 10.4 and newer currently.

Banshee 1.4 on Mac OS X 10.5

Because this is the first release of Banshee on Mac OS X, we are calling it a beta quality technology preview. This means that there are some known stability issues, and certainly some missing features, but it's good enough that we really encourage people to start trying it and filing bugs.

Some of the missing features for OS X (also known as "places where we are eagerly looking for new contributors"):

  • Hardware Backend
    Without a backend implementing interfaces in the Banshee.Hardware namespace, the OS X release does not feature any device support (no audio CDs, no digital audio players, no CD burning). For ambitious developers familiar with hardware APIs in OS X, following the HAL backend for Linux/FreeBSD is a great place to start!

  • Embedded Video
    Currently the Quartz video backend in GStreamer does not implement GstXOverlay. Work needs to be done to make this happen (even though obviously Quartz is not X11), or specific embedding can be done in Banshee itself. I'd prefer to see native GstXOverlay support however.

  • Screensaver/Power Management Inhibit
    In GNOME, this is implemented to prevent the system from sleeping or starting the screensaver when Banshee is in full screen mode (i.e. playing a movie).

  • Support for the Front Row/Apple TV remote
    This would just be a fun thing to write. Someone step up!

Finally, I have to give Eoin Hennessy a huge thanks for his work here again. Also to thank are the Songbird guys who have invested in making GStreamer usable on the Mac. We are looking forward to contributing in this space and working with Songbird, now that we are off the ground. What they have accomplished here is no small feat, not to be overlooked!

New API for customized mass storage device support

Implementing G1 support was done through the newly extensible mass storage device extension. That's right, extensions extending extensions. With this new API, it is now possible to add "polished" support for certain classes of mass storage media players, like the G1 or BlackBerry devices.

For instance, here's how the G1 is implemented:

Since this is just another Mono.Addin extension, new device support like this can be added outside of Banshee itself. I should note that this augments the portable_audio_player HAL specification. It's to be used when more than the generic mass storage functionality is desired for a device.

The Release Notes Speak

For a more in-depth overview of what Banshee 1.4 has to offer, please read the release notes. Here's a quick overview on what they cover:

  • Media player devices now support playlists (iPod, MTP/PlaysForSure included)
  • You can now have your device automatically sync with your library, or continue to manage it manually
  • I've implemented a brand new track editor that is fully extensible. It's very easy to add new pages!
  • Shiny new UI for now playing makes for a more entertaining "background" or "party" mode
  • There's now a tool that can rescan your library, adding new items or removing stale ones
  • File names/paths can now be automatically updated when metadata changes
  • Lots of minor UI improvements and polish
  • Stability and performance improvements

Try it already!

Banshee 1.4 is hands-down the best Banshee ever! Period! Packages should be available soon for your favorite Linux distribution, if they are not already (openSUSE and Ubuntu packages are at least ready now). And of course we have a shiny new Mac OS X .dmg!

Enjoy!

Update: Ryan wrote a great article about 1.4 over at Ars Technica. The comments are interesting. Is there anyone out there who has tried running Banshee on OpenSolaris? It works on FreeBSD...

This post powered by the "Similar to Eric Clapton" Last.FM radio station in Banshee 1.4.

Digg It!

November 13, 2008

Banshee 1.4 Released

This latest release brings out-of-the-box support for HTC G1 (Android) phones (complete with Amazon MP3 purchase importing, cover art syncing), the first technology preview release for Mac OS X, a new sexy track editor, media player playlist support and automated sync, collection rescanning, shiny UI updates, and performance and stability fixes! Get it now!

November 12, 2008

OpenWeek!

Ubuntuopenweeksmall

I'd like to thank everyone who participated in Ubuntu Open Week this time around, especially our presenters. For many presenters this was their first Open Week so the variety of talks continues to grow! Some Ars coverage of Mark's talk is here. Logs are posted for your viewing pleasure on the Open Week page.

Special thanks go out to Dylan McCall for handling the calendar attachment so that people can subscribe to the schedule. Also a shout out to the IRC team for their always solid support. Excellent work!

November 08, 2008

MonoTorrent 0.62

MonoTorrent 0.62 has now been released which addresses a few major and minor issues with the 0.60 release. Here's the details:

* Fixed regression in message handling which resulted in 0.60 not transferring properly. Caused by not running the right NUnit tests before tagging.
* Performance optimisation for sending/receiving messages
* Fixed bug creating torrents with 2gb+ files with TorrentCreator
* Fixed issue with udp sockets in the Dht code which could cause dht to stop sending/receiving messages

I'd highly recommend upgrading from 0.60 to 0.62 as soon as possible.

Binary - http://monotorrent.com/Files/0.62/monotorrent-0.62-bin.zip
Source - http://monotorrent.com/Files/0.62/monotorrent-0.62.tar.gz

November 06, 2008

Are you mapping those dlls?

One of the issues with writing cross platform applications is that if you do P/Invoke into a native library, the name of that library changes depending on the OS. Mono has built in support for selecting the right file at runtime. The problem is that it's hard to ensure that you've correctly mapped all the methods you P/Invoke.

So I wrote a little tool to do that for you. It was inspired by an attempt by Aaron Bockover which parsed the raw cs files. I figured it'd be much better to just parse the compiled assembly ;)


using System;
using System.Collections.Generic;
using System.Xml;
using Mono.Cecil;
using System.IO;

namespace DllImportVerifier
{
class MainClass
{
static void Main (string [] args)
{
args = new string [] { Environment.CurrentDirectory };
if (args.Length != 1) {
Console.WriteLine ("You must pass the path to the assemblies to be processed as the argument");
return;
}

List<string> assemblies = new List<string> ();

if (Directory.Exists (args[0])) {
assemblies.AddRange (Directory.GetFiles (args [0], "*.dll"));
assemblies.AddRange (Directory.GetFiles (args [0], "*.exe"));
}
else if (File.Exists (args [0])) {
assemblies.Add (args [0]);
}
else {
Console.WriteLine ("{0} is not a valid file or directory", args [0]);
return;
}

foreach (string assembly in assemblies)
ProcessConfig (assembly);
}

static void ProcessConfig (string assemblyName)
{
AssemblyDefinition assembly = null;
try {
assembly = AssemblyFactory.GetAssembly (assemblyName);
} catch {
Console.WriteLine ("{0} is not a valid .NET assembly", Path.GetFileName (assemblyName));
return;
}

List<string> dlls = new List<string> ();
try {
XmlTextReader doc = new XmlTextReader (assemblyName + ".config");
while (doc.Read ()) {
if (doc.Name != "dllmap")
continue;

string dll = doc.GetAttribute ("dll");
if (!dlls.Contains (dll))
dlls.Add (dll);
}
} catch {
// Ignore malformed or invalid config files. If the config is malformed,
// drop any successfully parsed dll maps
dlls.Clear();
}

List<string> unreferenced = VerifyReferences (assembly, dlls);
foreach (string dll in unreferenced)
Console.WriteLine("Assembly: '{0}' references '{1}' without mapping it", Path.GetFileName (assemblyName), dll);
}

static List<string> VerifyReferences (AssemblyDefinition assembly, List<string> dlls)
{
List<string> unreferenced = new List<string> ();
foreach (TypeDefinition type in assembly.MainModule.Types) {
foreach (MethodDefinition method in type.Methods) {
if (!method.IsPInvokeImpl)
continue;

string dll = method.PInvokeInfo.Module.Name;
if (!dlls.Contains (dll) && !unreferenced.Contains (dll))
unreferenced.Add (dll);
}
}

return unreferenced;
}
}
}

November 05, 2008

Advanced Topics in Inefficiency: Anonymous Methods

This is the first in what may be a series of posts on various theoretical (and not so theoretical) corner cases in common code. Despite being obtuse, these issues are useful to explore both to avoid inefficiencies and to better understand what's happening behind the code. Today's topic: anonymous methods.
class Foo()
{
    void Bar()
    {
        var thing1 = new Thing();
        var thing2 = new Thing();

        DoSomeStuff (() => thing1.Shimmy());
        DoOtherStuff(() => thing2.Shake());
    }
}
There is a potential memory problem with this method. It's not obvious from looking at the code, but both things must be garbage collected together. As long as there is an active reference to one, the other will live on as well. If 'Thing' is a heavy type, this could keep significant memory from being reclaimed on the heap. To better understand, let us look at how the C# compiler handles anonymous methods.

The above example demonstrates "local variable capture." This means local variables from the enclosing method body can be used inside closures (such as the two lambdas above). To accomplish this, the C# compiler shunts the values of the local variables to an object. The type of the object is generated by the compiler. In essence, the compiler turns the above code into this:
class Foo()
{
    class GeneratedTypeForMethodBar
    {
        public Thing thing1;
        public Thing thing2;

        public void AnonymousMethod1()
        {
            thing1.Shimmy();
        }

        public void AnonymousMethod2()
        {
            thing2.Shake();
        }
    }

    void Bar()
    {
        var closure_object =
            new GeneratedTypeForMethodBar();
        closure_object.thing1 = new Thing();
        closure_object.thing2 = new Thing();

        DoSomeStuff(closure_object.AnonymousMethod1);
        DoOtherStuff(closure_object.AnonymousMethod2);
    }
}
This is actually a very clever way of achieving local variable capture since it makes use of the CLI's pre-existing garbage collector to clean up the captured variables. The problem is, the compiler shunts all local variables to a single object. In our above example, the two anonymous methods do not reference any of the same local variables, but both local variables are stored in the same object. This can lead some captured variables to become prisoner variables: they are no longer needed, but they cannot be garbage collected. Suppose that our 'DoSomeStuff' method just invokes the delegate and returns. No problem. But now suppose that our 'DoOtherStuff' method holds on to the delegate, perhaps planing to invoke it later. Or suppose we were to return the second lambda, allowing the caller to hold the delegate as long as they please. That delegate holds a reference to the 'closure_object' which holds a reference to both Things, even though that delegate just needs 'thing2'. There is no way for any code to reach 'thing1' but it won't be garbage collected until we're done with 'thing2'.

Solution?

Well, we could modify the compiler to generate a type for each set of local variables that appear in only one anonymous method, like so:
class Foo()
{
    class GeneratedTypeForMethodBar1
    {
        public Thing thing1;

        public void AnonymousMethod()
        {
            thing1.Shimmy();
        }
    }

    class GeneratedTypeForMethodBar2
    {
        public Thing thing2;

        public void AnonymousMethod()
        {
            thing2.Shake();
        }
    }

    void Bar()
    {
        var closure_object1 =
            new GeneratedTypeForMethodBar1();
        closure_object1.thing1 = new Thing();

        var closure_object2 =
            new GeneratedTypeForMethodBar2();
        closure_object2.thing2 = new Thing();

        DoSomeStuff(closure_object1.AnonymousMethod);
        DoOtherStuff(closure_object2.AnonymousMethod);
    }
}
This poses problems as well. First of all, we are instantiating two (or more) generated-type objects rather than one. Object instantiation is not cheap and that could potentially slow down certain code. Also, this approach cannot be used to optimize more complex scenarios. Suppose we have five anonymous delegates, each referencing some of seven local variables like so: a {1 2} b {2 3} c {3 4 5} d {1 5 6} e {6 7}. In these situations we must default to the one-compiler-generated-type-for-everything approach.

Ultimately, the lesson here is just to be aware of these potential issues. If you find via profiling that objects are not being garbage collected and you make heavy use of anonymous methods, you might want to examine your closures to make sure this isn't causing the problem.

And what do people think about modifying the compiler as proposed above? Also, anyone who comes up with a better mechanism for local variable capture gets cool points. Double points if your solution doesn't require VM changes.

P.S. Thanks to Michael for help with this post.

BarrierHandle

Did you ever have a case where you have a big dataset which can be processed in parallel, so long as all threads finish Step 1 before any thread starts Step 2? Thing is, there's no built in class to handle this case.

AutoResetEvent won't do it because it because it only signals *one* of the waiting threads, not *all* of them. What you need is a manual reset event and some complex handling.

I give you BarrierHandle:


using System;
using System.Threading;

class SpectralNorm
{
public class BarrierHandle : System.Threading.WaitHandle
{
int current;
int threads;
ManualResetEvent handle = new ManualResetEvent (false);

public BarrierHandle (int threads)
{
this.current = threads;
this.threads = threads;
}

public override bool WaitOne()
{
ManualResetEvent h = handle;
if (Interlocked.Decrement (ref current) > 0) {
h.WaitOne ();
}
else {
handle = new ManualResetEvent (false);
Interlocked.Exchange (ref current, threads);
h.Set ();
h.Close ();
}

return true;
}
}

public static void Main(String[] args)
{
int threadCount = 5;//Environment.ProcessorCount;

// Lets assume that there's 20 units of data for each thread
int[] dataset = new int [threadCount * 20];

// This is the handle we use to ensure all threads complete the current step
// before continuing to the next step
BarrierHandle barrier = new BarrierHandle(threadCount);

// Fire up the threads
for (int i = 0; i < threadCount; i++) {
ThreadPool.QueueUserWorkItem (delegate {
int jjj = i;
try {
Step1 (dataset, jjj * 20, 20);
barrier.WaitOne ();
Step2 (dataset, jjj * 20, 20);
barrier.WaitOne ();
Step3 (dataset, jjj * 20, 20);
} catch (Exception ex) {
Console.WriteLine (ex);
}
});
}

System.Threading.Thread.Sleep (3000);
}

private static void Step1 (int[] array, int offset, int count)
{
Console.WriteLine ("Step1: {0} - {1}", offset, offset + count);
Thread.Sleep (500);
}
private static void Step2 (int[] array, int offset, int count)
{
Console.WriteLine ("Step2: {0} - {1}", offset, offset + count);
Thread.Sleep (500);
}
private static void Step3 (int[] array, int offset, int count)
{
Console.WriteLine ("Step3: {0} - {1}", offset, offset + count);
Thread.Sleep (500);
}
}

November 04, 2008

Election Day

In the United States, today is election day. If you are an eligible voter in the United States, it is your civic duty to vote.

I voted absentee two weeks ago, for Obama, and Jim Martin (GA Senatorial election), as well as a number of other county- and state-specific things. As far as things go, I’d really like it if the rest of you voted for Senator Obama as well, but if you are voting for another candidate then thank you for voting regardless.

Special shout-out to those in California: if you voted No on Proposition 8, rock on. There is no reason that same-sex couples should be denied the same rights and privileges as other couples. Not a damn one.

To those not in the US, I’m sure there is a fair portion of you who absolutely couldn’t care less about our elections, so (WARNING: NSFW) here’s a YouTube video to check out instead.

November 03, 2008

... and we're off!

Day 1 of Ubuntu OpenWeek is wrapping up.

Two minor changes to the schedule, we've added an Ubuntu training session with Billy Cina and Belinda Lopez on Wednesday and the fabulous Kurt Von Finck will be giving out recommendations and tips on "How to make smart buying decisions as a free software user" on Thursday.

Demand for slots has been high this time around so we're doing our best to jam as many topics in there. See you all bright and early tomorrow - Jono will be around to wrangle things as I will be taking a day off to go vote.