Skip to content

Instantly share code, notes, and snippets.

@budu
Created July 11, 2010 22:42
Show Gist options
  • Save budu/471910 to your computer and use it in GitHub Desktop.
Save budu/471910 to your computer and use it in GitHub Desktop.
A simple (headless) MP3 player written in Clojure using the clj-audio library.
(ns player
(:use clj-audio.core
clj-audio.sampled)
(:import javax.sound.sampled.SourceDataLine
java.io.File))
;;;; playlist
(def music-file-extensions ["mp3"])
(defn extension [f]
(let [n (.getName f)
i (.lastIndexOf n ".")]
(.substring n (+ i 1))))
(defn music-file? [f]
(when (.isFile f)
(some #(= % (extension f))
music-file-extensions)))
(defn music-files [& paths]
(apply concat
(map #(vec (filter music-file?
(file-seq (File. %))))
paths)))
(defn playlist [songs current action]
(condp = action
:previous (when (> @current 0)
(swap! current dec)
(nth songs @current))
:next (when (< @current (dec (count songs)))
(swap! current inc)
(nth songs @current))
:random (do (swap! current (constantly (rand-int (count songs))))
(nth songs @current))
(nth songs @current)))
(defn make-playlist [paths]
(let [songs (apply music-files (if (coll? paths)
paths
[paths]))
current (atom 0)]
(fn [action] (playlist songs current action))))
;;;; player actions
(def *player-actions* (ref clojure.lang.PersistentQueue/EMPTY))
(defn clear-actions []
(dosync
(ref-set *player-actions*
clojure.lang.PersistentQueue/EMPTY)))
(defn send-action [action & actions]
(dosync
(alter *player-actions* #(apply conj % action actions))))
(defn pop-current-action []
(dosync
(let [action (peek @*player-actions*)]
(when action
(alter *player-actions* pop))
action)))
(defn wait? [action]
(or (= action :pause)
(= action :stop)))
;;;; player
(defn read-file [file]
(try (->stream file)
(catch Exception e
(println "Error reading " file "\n" e))))
(def default-sleep-duration 20)
(defmacro sleep-while [test]
`(while ~test (Thread/sleep default-sleep-duration)))
(defn play_ [source audio-stream mark]
(sleep-while (running? source))
(future (-> audio-stream
(skip @mark)
decode
(play-with source)))
(sleep-while (not (running? source))))
(defn action-loop [source audio-stream]
(loop [action (pop-current-action)]
(when (and action (running? source))
(stop))
(Thread/sleep default-sleep-duration)
(cond (finished? audio-stream) nil
(and action (not (wait? action))) action
:default (recur (pop-current-action)))))
(def default-action :random)
(defn player [playlist]
(println "Starting player...")
(let [source (make-line :source *default-format* (* 4 1024))
mark (ref 0)]
(loop [song (playlist :play)]
(when song
(println "Currently playing:" song)
(if-let [stream (read-file song)]
(let [length (.available stream)
_ (play_ source stream mark)
action (action-loop source stream)]
(dosync
(ref-set mark (if (and (= action :play)
(not (finished? stream)))
(- length (.available stream))
0)))
(when (not= action :close)
(recur (playlist (or action default-action)))))
(recur (playlist default-action)))))
(close source))
(println "Player closed, bye!")
nil)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment