Idle Handler Projects
Often, I'll get a question like this: "How can I write a script that watches iTunes for [something to happen]; and when [that something happens] it triggers [something else] to happen."
What you want to do is write a Stay-Open script that contains an idle handler. An idle handler is a routine in a script that can periodically check the condition of iTunes during system idles—times when the system isn't doing anything else of vital importance. A Stay-Open application can use an idle handler to process data in real time while iTunes is playing.
This is no expert primer on writing idle handlers (to read some that are, click here), but what I'll attempt to illustrate is fairly simple and should give you an idea of the power of this sort of script.
Save as a Stay-Open Script
An idle handler routine always appears inside an on idle/end idle statement. Type or copy the script below into Script Editor and select Save As from Script Editor's File Menu. For Format select "Application". Now check the box that says "Stay Open". Finally, name it whatever you like and click the "Save" button.
on idle tell application "iTunes" copy name of current track to x beep return 3 end tell end idle
This is a Stay-Open script. If you run this it will copy the name of the current track to the variable "x" and beep approximately every three seconds until you click on Quit in its menu. (The only reason the beep is in there is so you know the script is doing something.)
This script is active aproximately every 3 seconds by virtue of the value in the return statement. This is called the idleInterval. If you don't set this, the default idleInterval will be 30 seconds. I'm going to use a 10 second rate throughout the rest of the examples. We don't want to be processor hogs.
Now, here is what we want this script to do:
1) Check iTunes to see what is playing...
2) Determine if the song playing "now" is the same or different than the one playing the last time the script checked during an idle...
3) And if it is a different song (or if it's the first time the script is checking), DO SOMETHING.
Check iTunes
We need to store the name of the current song so that we can determine if it has changed on subsequent checks of the script:
global latest_song set latest_song to "" on idle tell application "iTunes" copy name of current track to current_tracks_name if current_tracks_name is not latest_song then copy current_tracks_name to latest_song end if return 10 end tell end idle
When the script is first run, latest_song is blank (I have declared it a global variable because it must be available for use in the idle handler and the implicit run handler—if you don't know what this means, it's OK). When the script performs the idle routine it will get the current track's name, copy it to current_tracks_name, and compare it to latest_song. If the two strings are different (they will be the first time and if there is a new track playing), current_tracks_name is copied to latest_song. Then, every ten seconds, the script checks iTunes again.
The focal point of our script is that "if-then" statement; that's the point where we know something in iTunes is different, and anything we want to happen as a result of that change should be performed at this point. For example, we have to store the new track's name in order for the script to function correctly. How about if we also have a dialog pop up for two seconds with the name of the new track:
global latest_song set latest_song to "" on idle tell application "iTunes" copy name of current track to current_tracks_name if current_tracks_name is not latest_song then copy current_tracks_name to latest_song -- here's where you DO SOMETHING... display dialog current_tracks_name buttons {"*"} default button 1 giving up after 2 end if return 10 end tell end idle
We have gotten a Stay-Open script to keep watch over iTunes and perform a trick every time a new track starts (within a ten second window, anyway).
Refine It
Here is the same Stay-Open script with the addition of an explicit run handler:
global latest_song property okflag : false on run tell application "System Events" if not (exists process "iTunes") then return end tell set latest_song to "" end run on idle tell application "iTunes" copy name of current track to current_tracks_name if current_tracks_name is not latest_song then copy current_tracks_name to latest_song display dialog current_tracks_name buttons {"*"} default button 1 giving up after 2 end if return 10 end tell end idle
I have created an explicit run handler contained in the "on run/end run" statement. The run handler can perform any initialization routines we need to get started. Here it checks to see if iTunes is running and if iTunes isn't running the script quits (Leopard and later can use just "if not (application "iTunes" is running) then..."). Otherwise, if iTunes is running, the script can carry on and do whatever it does because it gets to stay open.
More Thoughts About Idle Handlers and iTunes
What else besides current track would be useful to check (or "poll") in real time? How about polling player position? Any value over :30 seconds and play the next track. That's how the script Needle Drop works. You could poll player state and any time iTunes was stopped it shuffled the playlist. Make a sleep timer by summing the lengths of tracks played. You get the idea.
In the examples above I used track's name as the variable to compare to see if a new track started playing. It is possible, of course, for two or more tracks to have the same name, and it is furthermore possible for two or more tracks with the same name to play in succession. Unusually rare, but possible. Thus it would be better to use a unique identifier such as a track's persistent ID or database ID, both of which will be unique for every track.
It is extremely important to end your idle routine with the "return value" statement. Unless you do, the last result, whatever it was, is the one that will be returned as the idleInterval value (although a non-numeric value will not change the idle rate). Set it to 0 to leave the rate unchanged. Also, do not set your idleInterval too low or perform complex routines too frequently. It can disrupt playback and possibly make other processes bumpy. A Stay-Open script should run quietly in the background.
There are circumstances when a running idle handler may not be able to access some iTunes properties. Several correspondents have noted that when one of iTunes' modal windows is open, AppleEvents are inaccessible. A modal window is one like the View Options Window or Info Window. This is done to let iTunes have exclusive access to the database, effectively locking you out. Therefore, attempting to get the value of a property like player state with an idle handler in a Stay-Open script when such a window is open could generate an error. In other words, Stay-Open scripts are safer and perform tasks better when iTunes is running unattended. Just so you know.
ADDENDA: Correspondent Dave Saunders suggests ignoring the possible errors by using a try statement, such as:
on idle tell application "iTunes" try if player state is not stopped then return 5 on error return 5 end try end tell -- do something here return 5 end idle [upm] click to open in Script Editor
This snippet is a little out of context, however you can see that by using the try statement to check the player state, as Dave reported, "it doesn't actually matter what causes the error, if there is one, just get out of there and try again in five seconds." Or whatever your idleInterval may be.
I would advise against writing a Stay-Open script that modifies track or playlist info unless you are absolutely sure you know what you are doing.
It may also be handy for you to learn how to write quit handlers which enable a Stay-Open script to perform some housekeeping or what have you before quitting itself. Information on quit handlers can be found at the links just below.
That's about all I know. Let me know what you figure out.
For more information on building idle handlers, have a look at these sites:
The AppleScript SourceBook: "Idle Thoughts"
unScripted: Idle Handlers
AppleScript Language Guide: Stay-Open AppleScripts