current-song if the song object in current-song no longer matches the file that the current-idx slot says should be playing. Then, if you call this function after any manipulation of the playlist that could possibly put those two slots out of sync, you're sure to keep current-song set properly. Here are update-current-if-necessary and its helper functions:

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

(unless (equal (file (current-song playlist))

(file-for-current-idx playlist))

(reset-current-song playlist)))

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

(if (at-end-p playlist)

nil

(column-value (nth-row (current-idx playlist) (songs-table playlist)) :file)))

(defun at-end-p (playlist)

(>= (current-idx playlist) (table-size (songs-table playlist))))

You don't need to add locking to these functions since they'll be called only from functions that will take care of locking the playlist 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[308] and whose title explains why no music is playing. Here's some code to define two parameters, *empty-playlist-song* and *end-of-playlist- song*, each set to a song with the file named by *silence-mp3* as their file and an appropriate title:

(defparameter *silence-mp3* ...)

(defun make-silent-song (title &optional (file *silence-mp3*))

(make-instance

'song

:file file

:title title

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

(defparameter *empty-playlist-song* (make-silent-song 'Playlist empty.'))

(defparameter *end-of-playlist-song* (make-silent-song 'At end of playlist.'))

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

(defun reset-current-song (playlist)

(setf

(current-song playlist)

(cond

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

((at-end-p playlist) *end-of-playlist-song*)

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

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

(with-column-values (file song artist album id3-size) song-db-entry

(make-instance

'song

:file file

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

:id3-size id3-size)))

(defun empty-p (playlist)

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

Now, at last, you can implement the method on maybe-move-to-next-song that moves current-idx to its next value, based on the playlist's repeat mode, and then calls update- current-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-locked (playlist)

(when (still-current-p song playlist)

(unless (at-end-p playlist)

(ecase (repeat playlist)

(:song) ; nothing changes

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

(:all (setf (current-idx playlist)

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

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

(update-current-if-necessary playlist))))

Manipulating the Playlist

The rest of the playlist code is functions used by the Web interface to manipulate playlist objects, including adding and deleting songs, sorting and shuffling, and setting the repeat mode. As in the helper functions in the previous section, you don't need to worry about locking in these functions because, as you'll see, the lock will be acquired in the Web interface function that calls these.

Adding and deleting is mostly a question of manipulating the songs-table. The only extra work you have to do is to keep the current-song and current-idx in sync. For instance, whenever the playlist is empty, its current-idx will be zero, and the current-song will be the *empty-playlist-song*. If you add a song to an empty playlist, then the index of zero is now in bounds, and you should change the current-song to the newly added song. By the same token, when you've played all the songs in a playlist and current-song is *end-of-playlist- song*, adding a song should cause current-song to be reset. All this really means, though, is that you need to call update-current-if-necessary at the appropriate points.

Adding songs to a playlist is a bit involved because of the way the Web interface communicates which songs to add. For reasons I'll discuss in the next section, the Web interface code can't just give you a simple set of criteria to use in selecting songs from the database. Instead, it gives you the name of a column and a list of values, and you're supposed to add all the songs from the main database where the given column has a value in the list of

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату