Local Track, Shared Track, or iTunes Store Preview?

Monday, 2006-11-20; 02:13:00



[UPDATE: I've found one other caveat while continuing to develop TuneTagger. The article has been updated and the edits have been explicitly noted.]

One of the major problems I've been dealing with when developing TuneTagger is the detection of whether the currently playing track is a local track on the user's hard drive, a shared track residing on a shared iTunes library of someone on the local network, or a preview stream playing from the iTunes Store. This is a major problem, especially in TuneTagger, because it affects what information about the track you can retrieve (via AppleScript) or receive (via an iTunes notification).

So, I've decided to document my findings here in case anyone finds this information useful. This all applies to the latest version of iTunes as of today, version 7.0.2.

First, AppleScript.


AppleScript

Of course, although you can access a wealth of information about a given track, AppleScript throws up some minor hoops for you to jump through to figure out what kind of track is currently playing.

Remote Tracks from Shared Libraries

If you're playing a track from a shared iTunes library, you can access pretty much all of the same information that you can about a local track. The track name, artist, album, and all other metadata will come through fine. One exception is the track's "location", which is where the file is stored on the hard disk -- accessing this about a shared track will generate an error. The other exception is the album art. That's also restricted: you can't even see it when browsing a shared library via iTunes.

How do you detect if the currently playing track is a remote track? Well, if you looked in the iTunes' dictionary, you'd see a "shared track" class. So that's one way of doing it -- simply ask for class of current track, and if it returns "shared track", then there you go. (Note that you'll need to coerce it to a string first by adding an as string to the end of the call, since the result is usually a class property.) Additionally, you could query its kind; remote tracks will have a "(remote)" appended to the end of the kind string. Unfortunately, this string changes depending on what language under which the user runs iTunes, so it might be better to stick with the former method.

Local Tracks

This one's the easiest. The class of a local track is "file track". You could also access its kind and check whether or not it has "(remote)" appended to the end, but again the localization caveat applies.

iTunes Store Previews

If you're previewing something from the iTunes Store, good luck getting any information about the track via AppleScript. You can't. Even telling iTunes to give you the current track via AppleScript fails. Apparently the iTunes Store and AppleScript don't like each other. (I've thought about this, and it's possible that Apple simply doesn't want anybody scripting the iTunes Store at all -- although I can't imagine why just getting information about the currently previewing song would be harmful even one bit.) Naive applications or scripts will toss errors and/or crash when you start previewing something from the iTunes Store.

Interestingly, although trying to retrieve the current track fails when the current track is an iTunes Store preview, AppleScript does in fact return a value when requesting class of current track. The return value is, amusingly, simply "property". You can even coerce this to a string.

So you have two options here: you could surround a current track line by a try/on error/end try block, and assume any tracks that throw an error are iTunes Store previews. Or, any tracks that return "property" as their class should also be iTunes Store previews. This seems to apply to iTunes Store video previews as well, but I can't guarantee this holds in all situations.

Note that you can access the player state (i.e.: playing, paused, or stopped) even when previewing a song from the iTunes store -- that's the one other piece of information you are able to retrieve. But it's not too helpful, usually.

Other

Browsing the iTunes AppleScript dictionary, it looks like there are two other kinds of tracks: a "URL track" and a "audio CD track". The former is for internet streams (like internet radio stations), and the latter is for tracks played directly off an audio CD. Again, for these you can simply query the class of the currently playing track.


iTunes Notifications

To integrate iTunes notifications in your Cocoa application, you'll need to place the following line somewhere in your code:

[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(receivediTunesNotification:) name:@"com.apple.iTunes.playerInfo" object:nil];


self is the object or class that will receive the notification, and receivediTunesNotification: is the method that gets called when a notification is generated. A method prototype that would match this would be -(void)receivediTunesNotification:(NSNotification *)theNotification . Then, in the called method, you can access the name of the generated notification using [theNotification name], and the dictionary object that accompanies the notification using [theNotification userInfo]. So if you wanted to access the track name of the currently playing iTunes track after it generated a notification, you would use something like [[theNotification userInfo] objectForKey:@"Name"]; .

Local Tracks and Streams

When you receive an iTunes notification that has a name of "com.apple.iTunes.playerInfo", the key to figuring out if this is a local track or not is to look for the "Location" dictionary key. If the notification includes this key and its corresponding value, you've got a local track.

Well, not necessarily. This "Location" key is also included for internet streams, or URL tracks in AppleScript-parlance. In this case, a quick check at whether the value for this key is prefaced with "file://" or "http://" will give you the difference between a true local track and a track representing a stream.

[UPDATE: It turns out that you can still have a local track even if the iTunes notification lacks a "Location" key. This occurs if the user is playing a song off of an iPod or other item that shows up in iTunes' "Devices" source sublist. This is probably because sometimes the file system of the device is unavailable or unmounted (as iPods are when disk use isn't activated), so the actual file representing the song may not always be accessible. So, unfortunately, in these cases, you need to actually make an AppleScript call to get the class of current track to tell whether the file is actually on a local device or not.]

Other information included in these notifications depends on whether the ID3 tags are entered into iTunes. If they're not, the keys for those pieces of information won't be included with the notification. Info generated by iTunes notifications include track name, genre, total time, year, track number, an iTunes Store URL, album, composer, track count, disc number, artist, disc count, album artist, and whether the track is part of a gapless album. Oh, and the current player state.

The fact that the amount of information changes depending on what's available is key. This means you cannot be naive and assume that an iTunes notification will always include the album name, or the year, or any other piece of info. In Cocoa, for example, you would need to check whether [[theNotification userInfo] objectForKey:@"Album"] is equal to nil or not before using that information elsewhere in your application.

Everything Else

This is where it gets ugly. iTunes Store previews, shared library tracks, and, surprisingly, tracks played directly off an audio CD [UPDATE: this list also includes songs which are played directly off a local physical device like an iPod] all do not include the "Location" key in their notification dialogs. So it's not possible to detect which of these three kinds of tracks you're dealing with. If it's necessary to discern between them, you might want to consider making a quick "class of current track" AppleScript call to iTunes.

Be aware that even iTunes Store previews will generate notifications with the attributes of the previewing song. You'll get the full gamut of information about the name, artist, album, year, etc, which is kind of nice. But you won't be able to use a lack of this information to determine whether the current track is an iTunes Store preview or not.

Note that there is a separate "com.apple.iTunes.sourceInfo" notification that is generated when you first connect to a shared iTunes library. I initially thought that this was generated every time you started playing a shared track, but this is not the case. So you can only detect when you connect to a shared library, but not whether the current song is from a shared library or not.


If you're making an application that makes any use of the attributes of the currently playing iTunes song, it's worth knowing these pitfalls and advantages to using both AppleScript and iTunes notifications. Armed with this information, you can avoid common crashes caused by inappropriate assumptions when using these song attribute retrieval methods.


Technological Supernova   Software Development   Older   Newer   Post a Comment