PDA

View Full Version : Extensible Track Information Interface



gerph
2008-02-03, 15:17
The problem:

Recently I've been playing with the Lyrics plugin. Whilst it's very nice (really, it is), it's somewhat frustrating to have to go to the Extras menu to get to lyrics. It doesn't seem right. It should be a property of the track information. So I wanted to make it possible for it to be on the track information page.

The TrackInfo.pm plugin provides this menu, and has a number of hacks in it. It has these hacks because the menu is fixed, and non-extensible by external plugins. The hacks I'm referring to are for the AudioScrobbler plugin and the Favorites selection. These should really be in the relevant plugins. Briefly I toyed with the idea of hacking the lyrics support in. But that would be bad and I would hate myself for it.

Instead I wanted to make a menu that could be extended by plugins. The audioscrobbler code could move to the audioscrobbler plugin and the favourites code to its plugin. And the lyrics plugin could register to extend the menu with a Lyrics link. Other plugins, like the biography plugin, or the album review plugin could provide other information. The world would be bright and smiley and there would be peace.


My solution:

My solution was simple. Remove the hardcodedness of the information items and create a list of registered 'information providers'. Each information provider can add things to the menu, hopefully which are relevant to the track. The providers are managed in a simplified form of dependency list. Each provider can be 'before' or 'after' another named provider, or they may be a member of a group ('isa').

For example, the 'contributors' provider is 'after' the 'title' provider. This way, plugins can place their items in logical positions in the tree. For example, the biography plugin might place itself 'after' the 'contributors'.

The grouping of items allows items to be placed into set groups where the order isn't relevant, for example the sample rate and bit rate can all go in a 'fileinfo' group. It doesn't matter what order they appear (although for consistency, they appear in alphabetical order if no other order is defined).

This was how I intended to make the system work, with multiple clients registering information providers, and each provider giving new menu items as it saw fit.

Because I understand that SlimServer is used in a larger way in SqueezeNetwork, it's obviously important that the dependency calculations weren't performed for every operation - after all they stay the same once things have been registered. So, I would cache the order once we'd worked it out and only recalculate when things changed.

That was the design.


The implementation:

First, I wanted to prototype the code. To see that it was actually going to work in the way that I wanted it to and didn't mean that I ended up writing a lot of ugly code. The implementation was amusing, but not too difficult. I took the basics of the list that SlimServer already had and mocked up a prototype which added these things to a set of lists and worked out an order.

The prototype was a success; you can find the code at :

http://usenet.gerph.org/SlimServer/TrackInfo/testlist.txt

and the test example code generates the following output :

http://usenet.gerph.org/SlimServer/TrackInfo/testlist-output.txt

Basically this is demonstrating the the list ordering works as desired, with each item being ordered correctly wether following one another, or within groups.

Three 'top level' groups are used - 'top', 'bottom' and 'middle'. At the moment I've hung almost everything off 'top'. Essentially it means that items can be placed in absolute positions as well as relative ones. So if a plugin always wants to appear at the top of the menu it always specifies 'after' => 'top' (or possibly 'isa' => 'top', depending on the type of behaviour they want).

Once the prototype was seen to work and that the coding isn't too ugly, I moved the main functions into the TrackInfo.pm source, and replaced all of the inline 'if' statements with if statements wrapped in function calls. This makes the code a little larger, but more manageable. I don't like putting inline code inside the registration functions because it feels more messy - this way the ordering information on registration is kept apart from the implementation.

In doing this I also noticed that the 'ReplayGain' and 'AlbumReplayGain' items were separated by the 'Rating'. This looked stupid, so I put the two ReplayGains together, and left Rating following them. Such things are easier when you're dealing with relative positions, I think.

Having implemented all the functions, I began to look at the AudioScrobbler hack. Because of the way the code is structured, this could be lifted almost wholesale from the TrackInfo.pm code and moved into the AudioScrobbler plugin. This has two uses - 1) it stops the collusion between AudioScrobbler and TrackInfo, and 2) it gives other plugin authors a useful guide for 'Oh, *that* is how you add items to the TrackInfo menu'.

I moved on to the Favourites item. This has hacks in 2 ways - firstly, it uses the magic string 'FAVORITES' to mean 'change me when you come to work out what line to draw'. This seemed a mess, so I made the line that is used accept a coderef. If you have a coderef in place of text, the code will be called to work out the text to use. Secondly, there was a special case of the 'type' which performs different operations when the item is selected. This was moved to be a coderef as well.

At this point, I couldn't work out whether the Favourites handling should be in Slim::Utils::Favorites or Slim::Plugin::Favorites. So I didn't move it. It would be trivial to do so in the future.

The diffs for my changes can be found here :

http://usenet.gerph.org/SlimServer/TrackInfo/registered-info-entries.diff


Possible uses:

I've already mentioned a few possible uses, but here's a quick list of things I can think of (which includes those things that already exist) :

* AudioScrobbler - ban / love this track
* Album review (an item below the album name?)
* Artist biography (an item below the contributors?)
* Lyrics (an item at the bottom of the list?)
* Favorites selection (similar to ratings, so maybe group these?)
* Ratings (changing the ratings for a track by a sub-menu item ?)
* Genre add to 'random mix', or remove from 'random mix'?
* Other meta data, like 'Involved people', or a list of the instruments the band members play ?
* Label released under

I'm sure there are other uses, but those are only the ones I can think of right now.


Using the interface:

As I have mentioned above, the interface has an example in the AudioScrobbler plugin which should be pretty clear. I've tried to document the functions I've got so that they're readable (doxygen style, because that's what I use at work - apologies as that's not the prevalent style in SlimServer).

Essentially a plugin would register a new information provider when it initialises :

registerInfoProvider('<name>', (
<position>,
'func' => \&<function-to-call>
'private' => <some private value>
);

where <name> is the name this information provider is to be given (so that it's available to others to be positioned relative to).
<position> may be one of :
'before' => '<thing-which-should-follow-this>'
or
'after' => '<thing-which-should-precede-this>'
or
'isa' => '<thing-which-this-is-a-member-of>'
<function-to-call> is a function which takes :
$client (the client this refers to)
$private (any private value, so that the function can be reused if necessary)
$url (the url object for the track item)
$track (the track object for the track)
<private> can be omitted and is intended to allow same function to provide multiple types of data.

For example, to place a 'change-rating' entry after the regular Rating item, you would use :

Slim::Buttons::TrackInfo::registerInfoProvider(
'change-rating', (
'after' => 'rating',
'func' => \&infoChangeRating
);

The function which is called might be something like this :

sub infoChangeRating
{
my ($client, $private, $url, $track) = @_;

Slim::Buttons::TrackInfo::addInfo($client,
"Set track rating",
\&functionToSetRating);
}

Any Slim::Buttons::TrackInfo::addInfo calls which are made will accumulate menu items. The text string above is untranslated. Obviously you'd use the translated form (but it's easier to write like that for an example). The function reference given above will be called when the user presses 'right' over the item. It will be called passing the $client parameter as its only variable.

If new menus are required, they can be pushed into in the usual way.

If the text string, above, was a coderef instead of a string, the code would be called, passing the $client parameter. The function called should return the string to display.


Epilogue:

That about covers what I wanted, why I was doing it, how I planned to do it, and how to use it. I didn't implement the necessary bits to hook in to the Lyrics plugin. I reckon the first hurdle is getting something like this into the core and then I can worry about updating the plugins to use it.

I don't know if this sort of functionality is desired by Logitech, but it seems to me that with everything else being so extensible, the TrackInfo menu has been missed out.

Also, it seems to me that if the information is managed in a structured way then passing this track information in the same form to the Jive remote becomes far, far simpler, which is a nice bonus as well.

Anyhow, I hope that the developers will consider the patch for inclusion.

andyg
2008-02-03, 15:26
On Feb 3, 2008, at 5:17 PM, gerph wrote:

>
> The problem:
>
> Recently I've been playing with the Lyrics plugin. Whilst it's very
> nice (really, it is), it's somewhat frustrating to have to go to the
> Extras menu to get to lyrics. It doesn't seem right. It should be a
> property of the track information. So I wanted to make it possible for
> it to be on the track information page.

Nice, could you file an enhancement request and attach your patch?
We'll look at it for a future version.

andyg
2008-02-03, 15:26
On Feb 3, 2008, at 5:17 PM, gerph wrote:

>
> The problem:
>
> Recently I've been playing with the Lyrics plugin. Whilst it's very
> nice (really, it is), it's somewhat frustrating to have to go to the
> Extras menu to get to lyrics. It doesn't seem right. It should be a
> property of the track information. So I wanted to make it possible for
> it to be on the track information page.

Nice, could you file an enhancement request and attach your patch?
We'll look at it for a future version.

gerph
2008-02-03, 15:38
On Feb 3, 2008, at 5:17 PM, gerph wrote:

>
> The problem:
>
> Recently I've been playing with the Lyrics plugin. Whilst it's very
> nice (really, it is), it's somewhat frustrating to have to go to the
> Extras menu to get to lyrics. It doesn't seem right. It should be a
> property of the track information. So I wanted to make it possible for
> it to be on the track information page.

Nice, could you file an enhancement request and attach your patch?
We'll look at it for a future version.

Bug #6930 raised on this, as an enhancement request. Thanks for the quick reply :-)