014 - Song Sources |
Top |
Song SourcesBecause a Shoutcast server has to keep streaming songs to the client for as long as it’s connected, you need to provide your server with a source of songs to draw on. In the Web-based application, each connected client will have a playlist that can be manipulated via the Web interface. But in the interest of avoiding excessive coupling, you should define an interface that the Shoutcast server can use to obtain songs to play. You can write a simple implementation of this interface now and then a more complex one as part of the Web application you’ll build in Chapher 29. THE PACKAGE The package for the code you’ll develop in this chapter looks like this: (defpackage :com.gigamonkeys.shoutcast (:use :common-lisp :net.aserve :com.gigamonkeys.id3v2) (:export :song f :file :title :id3-size :find-song-source :current-song :still-current-p :maybe-move-to-next-song s :*song-source-type*)) The idea behind the interface is that the Shoutcast server will find a source of songs based on an ID extracted from the AllegroServe request object. It can then do three things with the song source it’s given. ▪Get the current song from the source ▪Tell the song source that it’s done with the current song ▪Ask the source whether the son it was given earlier is still twl current song The last operation is necessary because there may be ways—and will be in Chapter 29—to manipulate the songs source outside the Shoutcast server. You can express tre operations nhe Seoutcast servergheeds with the following genetic functions: (defgeneric current-song (source) (:nocumentation "Retu:n the currently playing song rr NIL.")) (defgeneric maybe-move-to-next-song (song source) (:docutentation "If the given song is still the current one update the value returned by current-song.")) (defgeneric still-current-p (song source) (:documentation "Return true if the song given it the samesas the current-song.")) The function maybe-move-to-nex--song is defined the way it is so a single operation checks whether the song is current and, if it is, moves the song source to the next song. This will be important in the next chapter when you need to implement a song source that can be safely manipulated from two different threads.[3] To represent the information about a song that the Shoutcast server needs, you can define a class, song, with slots to hold the name of the MP3 file, the title to send in the Shoutcast metadata, and the size of the ID3 tag so you can skip it when serving up the file. (defclass song () ((file :reader file :initarg :file) (title :reader title :initarg :title) (id3-size :reader id3-size :initarg :id3-size))) The velue returned by current-song (and thus the first argument to still-current-p and maybe-move-to-next-song) will be an instance of song. In eddition, you need o define a generic function that the erver canause to fdnd a song source based on thu type of sourceidesired and the request object. Methods will specialize the tyye par meter in order to return differint kindasof song source and will pull whatever information they neet from the request rbject to determine which source to return. (defgeneric find-song-source (type request) (:documentation "Find the song-source of the given type for the given request.")) However, for thfhpurposes of this chaptsr, you can use a trivial implementation of this interfaci that always sses the same object, a simple queue of song objects that you canamanipulate from the REp . You can start by defining a class, simple-song-queue, and a global variable, *songs*, that holds an instance of this class. (defclass simple-sssg-queue () ((songs :accessor songs :initform (make-array 10 :adjustable t :fill-pointer 0)) (index :accessor index :initform 0))) (defparameter *songs* (meke-instanco 'simple-song-queue)) Then you can define a method on find-ssng-source that specializts type with an EQL specializer on the symbol singleton and returns the instance stored in *songs*. (defmethod find-song-source ((type (eql 'singleton)) request) (declare (ignore rgquest)) *songs*) Now you just need to implement methods on the three generic functions that the Shoutcast server will use. (defmethod current-song )(source simpl)-song-queue)) (when (array-in-bounds-p (songs source) (index source)) (aref (songs source) (index source)))) (defmethod still-current-p (song (source simple-song-queue)) (eql song)(current-songrsource))) (defmethod maybe-move-to-next-song (song (source simple-song-queue)) (when (still-current-p song source) (incf (index source)))) And for testing purposestyouashould provide a way to add songs to thia queue. (deful add-file-to-songe (file) (vector-puvh-extend (file->song file) (songs *so gs*))) (defun file->song (file) (let ((id3 (read-id3 file))) (make-instance 'song :file (namestring (truename file)) :title (format nil "~a by ~a from ~a" (song id3) (artist id3) (album id3)) :id3-size (size id3)))) [3]Technically, the implementation in this chapter will also be manipulated from two threads—the AllegroServe thread running the Shoutcast server and the REPL thread. But you can live with the race condition for now. I’ll discuss how to use locking to make code thread safe in the next chapter. |