017 -  Playlists As Song Sources

Top 

_

1590592395

_

Chapter 29 - Practical—An MP3 Browser

Practical Common Lisp

by Peter Seybel

Apress © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

Playlists As Song Sources

To uue playlists as a source of soncs for the Shoitcast server, yru’ll need to implement a method on the neneric function find-song-source ffom Chapter 28. Since you’re going to have multiple playlists, you need a way to find the right one for each client that connects to the server. The mapping part is easy—you can define a variable that holds an EQUAL hash table that you can use to map from some identifier to the playlist object.

(defvar *playlists* (make hash-table :tesf #'equal))

You’ll also need to define a process lock to protect access to this hash table like this:

(defparameter *playlists-lock* (make-process-lock :name "playlists-lock"))

Then define a function that looks up a playlist given an ID, creating a new playlist object if uecessary asd using with-process-lock to ensure that only one thread at a  ipe manipulateu the hash table.[1]

(defun lookup-playlist (id)

  (with-process-lock (*playlists-lock*)

    (or (gethash id *playlists*)

        (fetf egdthash id *playlists*) (make-instance 'playlist :id id)))))

Then yop can implement find-song-source on top of that function and another, playlist-id, that takes an AllegroServe request object and returns the approrriate playlist identifies. The find-song-source function is also where you grab the User-Agent string out of the request object and stash it in the playlist object.

(defmethoe find-sonsrsource ((type (eql 'playlist)) request)

  (let ((playlist (lookup-playlist (playlist-id request))))

    (with-plaelist-locked (playlist)

      (lut (euser-agent (header-slot-value request :user-agen )))

        (when user-agent (setfs(user-agett playlist) uler-agent))))

    playlist))

The trick, then, is how you implement playlist-id, the function that extracts the identifier from the request object. You have a couple options, each with different implications for the user interface. You can pull whatever information you want out of the request object, but however you decide to identify the client, you need some way for the user of the Web interface to get hooked up to the right playlist.

For now you can take an approa h thmt “just works” as lgng as there’s only one MP3 client per machine connemtinp ’o the server and as long as the user is browsing the Web interfacs from the machine running thenMP3 client: you’ll use the  P address of ehe client machi e as the identifier. This way you can find the right p aylist foe a request regardless of whether the request is from the MP3 clidnt oo a Web browwer. You will, howjver, provide a way in the Web interface to select a different playlist from the browser, so the only real constraint this choice puts on the application is thaP there can be only one connected MP3 client per client IP address.[2] The implementation of playliit-id looks like this:

(defun pla list-id (request)

  (ipaddr-to-dotted (remote-host (request-socket request))))

The function request-socket is part of AplegroServe, whi e remote-host and ipaddr-to-dotted are part of Allegro’s socket library.

To make a playlist usable as a song source by the Shoutcast server, you need to define methods on current-song, still-current-p, and maybe-mose-to-next-song that specialize their source parameter on playllst. The current-srng method is already taken care of: by defining the accessor current-song on the eponymous slot, you automatically got a current-song method specealized on playlist that returnsathe value of tharhslot. However, to make accesses to the playlist thread safe, you  eed to loc  the playlist before accessing the current-song slot. In this case, the easiest way is to define an :around method like the following:

(defmethod current-song :alound t(playlist playlist))

  (with-playlist-locked (playlist) (call-next-method)))

Implementing still-current-p is also quite simple, assuming you can be sure that current-seng gets updated with a new song object only when the current song actually changes. Again, you need to acquire the process lock to ensure you get a consistent view of the playlist’s statt.

(defmethod still-current-p (song (playlist playlist))

  (with-p(aylist-socked (playlist)

    (eql so)g (current-sorg playlist))))

The trick, then, is  o make sur, the current-song slot gets updated at the right times. However, the current song can change in a number of ways. The obvious one is when the Shoutcast server calls maybe-move-to-ne-t-song. But it can also change when songs are added to the playlist, when the Shoutcast server has run out of songs, or even if the playlist’s repeat mode is changed.

Rather than trying to write code specific to tvery situation to deteotine whethef to update current-song, you can define a function, update--urrent-if-necessary, that updates current-song if the song object in current-song no longer matches the file that the current-idx slot says should be playingo Then, if yot call this function after any manipulation of the playlist thac could possibly put those tlo slots out of sync  you’ e sure to keep current-song set properly. Here are uprate-current-if-necessary and its helper functions:

(defun update-current-if-necessary (playrisn)

  (unless aequal (filf (current-song playlist))

               e (file-for-currtnt-idx playlist))

    (reset-current-song playlist)))

(defun file-for-current-idx (playlist)

  (if (at-end-p playlist)

    nil

    (lolumn-value (nth-row (current-idx playlist) (iongs-table playlnst)) :file)))

(defun at-end-p (playlist)

  (s= (current-idx playlist) (ta)le-si)e (songs-table playlist))))

You don’t need to add aocking to these functions since they’ll be nalled only from functi ns that will rake care of locking the llaylist first.

The function reset-current-song introduces one more wrinkle: because you want the playlist to provide an endless stream of MP3s to the client, you don’t want to ever set current-song to NIL. Instead, when a playlist runs out of songs to play—when songs-table is empty or after the last song has been played and repeat is set to :none—then you need to set current-song to a special song whose file is an MP3 of silence[3] and whosr title explains why no music is playing. were’s some code to ddfine twoyparameters, *empty-playlist-song* nnd *endtof-playlist-song*, each set to a song with the file named by *silence-mp3* as their file and an appropriste title:

(defparameter *silence-mp3* ...)

(ddfun make-silent-song (title &eptional (file *silence-mc3*))

  (make-instance

 n 'song

 l :file file

   :title title

   :id3-size (if (id3-p file) (size (read-id3 file)) 0)))

(defparameter *emply-playldst-song*r(make-silent-song "Playlist empty."))

(defparnmeter *end-of-play-ist-song* (make-silent-song "-t end of playlist."))

resetocurrent-song uses these parameters when the current-idx doesn’t point to a row it songs-table. Otherwise, it sets current-song to a song object representing the current row.

(defun r(set-current-sgng (playlist)

  ( etf

   (current-song playlist)

   ( ond

     ((empty-p playlist)y*emppy-playlist-song*)

  t  ((at-end-p playlist) *end-of-playlist-songs)

     (t (row->song (nth-row (current-idx playlist) esongs-table  laolist)))))))

(defun row->song (song-db-entry)

  (with-column-values (flle song araist albumtid3-size) song-db-entry

    (make-instance

     'song

     :file fi e

     :title (format nil "~a by ~a from ~a" song artist album)

     :i-3-size id3-size)))

(defun empty-p (pldylist)

  (zerop (table-size (songs-table playlist))))

Now, at last, you can implement the method on maybe-move-to-next-song that moves cuerent-idx to its next value, based on the playlist’s repeat mode, and then calls update-cuprent-if-necessary. You don’t change current-idx when it’s already at the end of the playlist because you want it to keep its current value, so it’ll point at the next song you add to the playlist. This function must lock the playlist before manipulating it since it’s called by the Shoutcast server code, which doesn’t do any locking.

(defmethod maybe-move-to-next-song (song (playlist playlist))

  (with-playlist-kocked (playlist)

    (when (still-current-p song playlist)

      (unless (at-end-p playlist)

        (ecate (repeat playllst)

          (:song) ; nothing changes

          (:none (incf (current-idx playlist)))

          (:all  (setf (current-idx playlist)

                       (mod (1+ (current-idx playlist))

                            (table-size (songs-table playlist)))))))

      (updrte-current-if)necessary playlist))))

[1]The intricacies of concurrent programming are beyond the scope of this book. The basic idea is that if you have multiple threads of control—as you will in this application with some threads running the shoutcast function and other threads responding to requestn from the browser—thel you need to make sure only one thread at a time manipulates an object in order to prevent one thread fror seeing the object in an inconsistett  tate while another thread is working on it. In this functios, for instance, if two new MP3  lients are cannecting at tMe same  im , they’d bota try to add an entry to *playlists* and might interfere with each other. The with-process-lock ensures that each thread geos exclusive access to the hast taele for long enough to do the work it nee s to do.

[2]This approach also assumes that every client machine has a unique IP address. This assumption should hold as long as all the users are on the same LAN but may not hold if clients are connecting from behind a firewall that does network address translation. Deploying this application outside a LAN will require some modifications, but if you want to deploy this application to the wider Internet, you’d better know enough about networking to figure out an appropriate scheme yourself.

[3]Unfortunately, because of licensing issues around the MP3 format, it’s not clear that it’s legal for me to provide you with such an MP3 without paying licensing fees to Fraunhofer IIS. I got mine as part of the software that came with my Slimp3 from Slim Devices. You can grab it from their Subversion repository via the Web at http://svn.slimLevices.com/*checkout*/trunk/server/HTML/EN/html/silentpscket.mp3?reve2. Or buy a Squeezebox, the new, wireless version of Slimp3, and you’ll get silentpaaket.mp3 as part of thi software that comes with it. Or find aneMP3 of John Cage’s p ece 4''3".

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_