Christophe Weblog Wiki Code Publications Music
1d4a92d7060082422dafc0eec2ec7782635d9f18
[squeeze-el.git] / squeeze.el
1 (defgroup squeeze nil
2   "Interact with Squeezebox media servers"
3   :prefix "squeeze-" 
4   :group 'applications)
5
6 (defcustom squeeze-server-address "localhost"
7   "Address for the Squeezebox server"
8   :group 'squeeze)
9 (defcustom squeeze-server-port 9090
10   "Port number for the Squeezebox server"
11   :group 'squeeze)
12 (defcustom squeeze-server-http-port 9000
13   "Port number for the Squeezebox HTTP server"
14   :group 'squeeze)
15 (defvar squeeze-mode-map
16   (let ((map (make-sparse-keymap)))
17     (define-key map (kbd "TAB") 'completion-at-point)
18     map))
19
20 (defun squeeze-unhex-string (string)
21   (with-temp-buffer
22     (let ((case-fold-search t)
23           (start 0))
24       (while (string-match "%[0-9a-f][0-9a-f]" string start)
25         (let* ((s (match-beginning 0))
26                (ch1 (url-unhex (elt string (+ s 1))))
27                (code (+ (* 16 ch1)
28                         (url-unhex (elt string (+ s 2))))))
29           (insert (substring string start s)
30                   (byte-to-string code))
31           (setq start (match-end 0))))
32       (insert (substring string start)))
33     (buffer-string)))
34
35 (defun squeeze-unhex-and-decode-utf8-string (string)
36   (decode-coding-string (squeeze-unhex-string string) 'utf-8))
37
38 (define-derived-mode squeeze-mode comint-mode "Squeeze"
39   "Major mode for interacting with the Squeezebox Server CLI.\\<squeeze-mode-map>"
40   (add-to-list 'completion-at-point-functions 'squeeze-complete-command-at-point)
41   (add-hook 'comint-preoutput-filter-functions 'squeeze-unhex-and-decode-utf8-string nil t)
42   (add-hook 'comint-preoutput-filter-functions 'squeeze-update-state nil t))
43
44 (defvar squeeze-control-mode-map
45   (let ((map (make-sparse-keymap)))
46     (define-key map (kbd "SPC") 'squeeze-control-toggle-power)
47     (define-key map (kbd "f") 'squeeze-control-play-favorite)
48     (define-key map (kbd "g") 'squeeze-control-refresh)
49     (define-key map (kbd "+") 'squeeze-control-volume-up)
50     (define-key map (kbd "-") 'squeeze-control-volume-down)
51     (define-key map (kbd "t") 'squeeze-control-toggle-syncgroup-display)
52     (define-key map (kbd ">") 'squeeze-control-next-track)
53     (define-key map (kbd "<") 'squeeze-control-previous-track)
54     (define-key map (kbd "s") 'squeeze-control-select-player)
55     (define-key map (kbd "a") 'squeeze-list-albums)
56     (define-key map (kbd "!") 'squeeze-control-reconnect)
57     map))
58
59 (defvar squeeze-control-current-player nil)
60
61 (defun squeeze-control-select-player (id)
62   (interactive
63    (list (or (get-text-property (point) 'squeeze-playerid)
64              (let ((name (completing-read "Select player: " (mapcar 'squeeze-player-name squeeze-players))))
65                (squeeze-player-playerid (squeeze-find-player-from-name name))))))
66   (setq squeeze-control-current-player id))
67
68 (defun squeeze-control-next-track ()
69   (interactive)
70   (squeeze-send-string "%s playlist index +1" squeeze-control-current-player))
71
72 (defun squeeze-control-previous-track ()
73   (interactive)
74   (squeeze-send-string "%s playlist index -1" squeeze-control-current-player))
75
76 (define-derived-mode squeeze-control-mode special-mode "SqueezeControl"
77   "Major mode for controlling Squeezebox Servers.\\<squeeze-control-mode-map>")
78
79 (defvar squeeze-control-inhibit-display nil)
80
81 (lexical-let ((buffer (get-buffer-create " *squeeze-update-state*")))
82   (defun squeeze-update-state (string)
83     ;; FIXME: we could make this a lot more elegant by using the
84     ;; buffer abstraction more
85     (if (cl-position ?\n string)
86         (let (done-something)
87           (with-current-buffer buffer
88             (insert string)
89             (setq string (buffer-string))
90             (erase-buffer))
91           (dolist (line (split-string string "\n"))
92             (when (squeeze-update-state-from-line line)
93               (setq done-something t)))
94           (when done-something
95             (unless squeeze-control-inhibit-display
96               (squeeze-control-display-players))))
97       (with-current-buffer buffer
98         (insert string)))
99     string))
100
101 (defconst squeeze-player-line-regexp
102   "^\\(\\(?:[0-9a-f]\\{2\\}%3A\\)\\{5\\}[0-9a-f]\\{2\\}\\) ")
103
104 (defun squeeze-find-player (id)
105   (dolist (player squeeze-players)
106     (when (string= id (squeeze-player-playerid player))
107       (return player))))
108
109 (defun squeeze-find-player-from-name (name)
110   (dolist (player squeeze-players)
111     (when (string= name (squeeze-player-name player))
112       (return player))))
113
114 (defun squeeze-update-power (player state)
115   (if state
116       (setf (squeeze-player-power player) state)
117     (let ((current (squeeze-player-power player)))
118       (setf (squeeze-player-power player)
119             (cond ((string= current "0") "1")
120                   ((string= current "1") "0"))))))
121
122 (defun squeeze-update-mixer-volume (player value)
123   (let ((current (squeeze-player-volume player))
124         (number (string-to-number value)))
125     (if (string-match "^[-+]" value)
126         (setf (squeeze-player-volume player)
127               (and current (max 0 (min 100 (+ current number)))))
128       (setf (squeeze-player-volume player) number))))
129
130 (require 'notifications)
131
132 (defun squeeze-update-state-from-line (string)
133   (cond
134    ((string-match "^players 0" string)
135     (setq squeeze-players (squeeze-parse-players-line string))
136     t)
137    ((string-match "^syncgroups" string)
138     (setq squeeze-syncgroups (squeeze-parse-syncgroups-line string))
139     t)
140    ((string-match "^albums" string)
141     (squeeze-parse-albums-line string)
142     t)
143    ((string-match squeeze-player-line-regexp string)
144     (let ((substring (substring string (match-end 0)))
145           (id (url-unhex-string (match-string 1 string))))
146       (cond
147        ((string-match "^playlist newsong \\(.*\\) \\([0-9]+\\)$" substring)
148         (let ((value (save-match-data (url-unhex-string (match-string 1 substring))))
149               (index (url-unhex-string (match-string 2 substring))))
150           (notifications-notify :title "Now playing" :body (encode-coding-string (format "%s: %s" index value) 'utf-8)))
151         t)
152        ((string-match "^power\\(?: \\([01]\\)\\)?" substring)
153         (let ((state (match-string 1 substring))
154               (player (squeeze-find-player id)))
155           (squeeze-update-power player state))
156         t)
157        ((string-match "^mixer volume \\(\\(?:-\\|%2B\\)?[0-9]*\\)" substring)
158         (let ((value (url-unhex-string (match-string 1 substring)))
159               (player (squeeze-find-player id)))
160           (squeeze-update-mixer-volume player value))
161         t))))))
162
163 (defface squeeze-player-face
164   '((t))
165   "Face for displaying players"
166   :group 'squeeze)
167 (defface squeeze-player-on-face
168   '((t :weight bold :inherit squeeze-player-face))
169   "Face for displaying players which are on"
170   :group 'squeeze)
171 (defface squeeze-player-off-face
172   '((t :weight light :inherit squeeze-player-face))
173   "Face for displaying players which are off"
174   :group 'squeeze)
175
176 (defface squeeze-mixer-face
177   '((t :weight bold))
178   "Face for displaying mixer information"
179   :group 'squeeze)
180 (defface squeeze-mixer-muted-face
181   '((t :weight light :inherit squeeze-mixer-face))
182   "Face for displaying mixer information when muted"
183   :group 'squeeze)
184 (defface squeeze-mixer-quiet-face
185   '((t :foreground "green3" :inherit squeeze-mixer-face))
186   "Face for quiet volume"
187   :group 'squeeze)
188 (defface squeeze-mixer-medium-face
189   '((t :foreground "gold" :inherit squeeze-mixer-face))
190   "Face for medium volume"
191   :group 'squeeze)
192 (defface squeeze-mixer-loud-face
193   '((t :foreground "OrangeRed1" :inherit squeeze-mixer-face))
194   "Face for loud volume"
195   :group 'squeeze)
196 (defface squeeze-mixer-muted-quiet-face
197   '((t :inherit (squeeze-mixer-muted-face squeeze-mixer-quiet-face)))
198   "Face for quiet volume when muted")
199 (defface squeeze-syncgroup-face
200   '((t :slant italic))
201   "Face for syncgroups"
202   :group 'squeeze)
203
204 (defun squeeze-mixer-compute-bar (vol width)
205   (let* ((exact (* width (/ vol 100.0)))
206          (nfull (floor exact))
207          (frac (- exact nfull))
208          (nblank (floor (- width exact))))
209     (format "%s%s%s"
210             (make-string nfull ?█)
211             (if (= width (+ nfull nblank))
212                 ""
213               (string (aref " ▏▎▍▌▋▊▉█" (floor (+ frac 0.0625) 0.125))))
214             (make-string nblank ? ))))
215
216 (defun squeeze-mixer-make-bar (vol width)
217   (let ((bar (squeeze-mixer-compute-bar vol width))
218         (lo (floor (* 0.65 width)))
219         (hi (floor (* 0.9 width))))
220     (concat "▕"
221             (propertize (substring bar 0 lo) 'face 'squeeze-mixer-quiet-face)
222             (propertize (substring bar lo hi) 'face 'squeeze-mixer-medium-face)
223             (propertize (substring bar hi) 'face 'squeeze-mixer-loud-face)
224             (propertize "▏" 'intangible t))))
225
226 (defvar squeeze-players ())
227 (defvar squeeze-syncgroups ())
228
229 (defun squeeze-send-string (control &rest arguments)
230   (let* ((process (get-buffer-process "*squeeze*"))
231          (string (apply #'format control arguments))
232          (length (length string)))
233     (unless (and (> length 0) (char-equal (aref string (1- length)) ?\n))
234       (setq string (format "%s\n" string)))
235     (if process
236         (comint-send-string process string)
237       (error "can't find squeeze process"))))
238
239 (defun squeeze-control-query-syncgroups ()
240   (interactive)
241   (squeeze-send-string "syncgroups ?"))
242
243 (defun squeeze-control-query-players ()
244   (interactive)
245   (squeeze-send-string "players 0"))
246
247 (defun squeeze-control-toggle-power (&optional id)
248   (interactive)
249   (unless id
250     (setq id (get-text-property (point) 'squeeze-playerid)))
251   (squeeze-send-string "%s power" id))
252
253 (defun squeeze-control-play-favorite (&optional favorite id)
254   (interactive "nFavourite: ")
255   (unless id
256     (setq id (get-text-property (point) 'squeeze-playerid)))
257   (squeeze-send-string "%s favorites playlist play item_id:%d" id favorite))
258
259 (defun squeeze-control-query-power (&optional id)
260   (interactive)
261   (unless id
262     (setq id (get-text-property (point) 'squeeze-playerid)))
263   (when id
264     (squeeze-send-string "%s power ?" id)))
265
266 (defun squeeze-control-volume-up (&optional id inc)
267   (interactive)
268   (unless inc (setq inc 5))
269   (unless id
270     (setq id (get-text-property (point) 'squeeze-playerid)))
271   (when id
272     (squeeze-send-string "%s mixer volume %+d" id inc)))
273
274 (defun squeeze-control-volume-down (&optional id inc)
275   (interactive)
276   (unless inc (setq inc 5))
277   (unless id
278     (setq id (get-text-property (point) 'squeeze-playerid)))
279   (when id
280     (squeeze-send-string "%s mixer volume %+d" id (- inc))))
281
282 (defun squeeze-control-volume-set (id val)
283   (interactive)
284   (squeeze-send-string "%s mixer volume %d" id val))
285
286 (defun squeeze-control-query-mixer-volume (&optional id)
287   (interactive)
288   (unless id
289     (setq id (get-text-property (point) 'squeeze-playerid)))
290   (when id
291     (squeeze-send-string "%s mixer volume ?" id)))
292
293 (defun squeeze-control-player-face (player)
294   (let ((power (squeeze-player-power player)))
295     (cond ((string= power "1") 'squeeze-player-on-face)
296           ((string= power "0") 'squeeze-player-off-face)
297           (t 'squeeze-player-face))))
298
299 (defun squeeze-control-listen ()
300   (squeeze-send-string "listen 1"))
301
302 (defun squeeze-accept-process-output ()
303   (while (accept-process-output (get-buffer-process "*squeeze*") 0.1 nil t)))
304
305 (defun squeeze-control-refresh ()
306   (interactive)
307   (let ((squeeze-control-inhibit-display t))
308     (squeeze-control-query-players)
309     (squeeze-accept-process-output)
310     (squeeze-control-query-syncgroups)
311     (dolist (player squeeze-players)
312       (squeeze-control-query-power (squeeze-player-playerid player))
313       (squeeze-control-query-mixer-volume (squeeze-player-playerid player))))
314   (squeeze-accept-process-output)
315   (squeeze-control-display-players))
316
317 (defvar squeeze-control-mixer-map
318   (let ((map (make-sparse-keymap)))
319     (define-key map (kbd "RET") 'squeeze-control-mixer-set-volume)
320     (define-key map [mouse-1] 'squeeze-control-mixer-mouse-1)
321     map))
322
323 (defun squeeze-control-compute-volume (pos)
324   (let* ((end (next-single-property-change pos 'keymap))
325          (start (previous-single-property-change end 'keymap)))
326     (/ (* 100 (- (point) start)) (- end start 1))))
327
328 (defun squeeze-control-mixer-mouse-1 (event)
329   (interactive "e")
330   (let* ((pos (cadadr event))
331          (val (squeeze-control-compute-volume pos))
332          (id (get-text-property pos 'squeeze-playerid)))
333     (squeeze-control-volume-set id val)))
334
335 (defun squeeze-control-mixer-set-volume ()
336   (interactive)
337   (let* ((val (squeeze-control-compute-volume (point)))
338          (id (get-text-property (point) 'squeeze-playerid)))
339     (squeeze-control-volume-set id val)))
340
341 (defvar squeeze-control-display-syncgroups nil)
342
343 (defun squeeze-control-toggle-syncgroup-display ()
344   (interactive)
345   (setf squeeze-control-display-syncgroups
346         (not squeeze-control-display-syncgroups))
347   (squeeze-control-display-players))
348
349 (defun squeeze-control-insert-player (player)
350   (insert (propertize (format "%20s" (squeeze-player-name player))
351                       'face (squeeze-control-player-face player)
352                       'squeeze-playerid (squeeze-player-playerid player)))
353   (when (squeeze-player-volume player)
354     (insert (propertize
355              (squeeze-mixer-make-bar (squeeze-player-volume player) 28)
356              'squeeze-playerid (squeeze-player-playerid player)
357              'keymap squeeze-control-mixer-map
358              'pointer 'hdrag
359              'rear-nonsticky '(keymap))))
360   (insert (propertize "\n" 'intangible t)))
361
362 (defun squeeze-control-display-players ()
363   (interactive)
364   (with-current-buffer (get-buffer-create "*squeeze-control*")
365     (let ((saved (point)))
366       (squeeze-control-mode)
367       (read-only-mode -1)
368       (erase-buffer)
369       (cond
370        (squeeze-control-display-syncgroups
371         (let ((syncgroups squeeze-syncgroups)
372               (seen))
373           (while syncgroups
374             (let ((names (getf syncgroups :sync_member_names))
375                   ;; new server version has sync_members and sync_member_names
376                   (members (split-string (getf syncgroups :sync_members) ",")))
377               (insert (propertize names 'face 'squeeze-syncgroup-face) "\n")
378               (dolist (member members)
379                 (let ((player (squeeze-find-player member)))
380                   (squeeze-control-insert-player player)
381                   (push player seen))))
382             (setq syncgroups (cddddr syncgroups)))
383           (insert (propertize "No syncgroup" 'face 'squeeze-syncgroup-face) "\n")
384           (dolist (player squeeze-players)
385             (unless (member player seen)
386               (squeeze-control-insert-player player)))))
387        (t
388         (dolist (player squeeze-players)
389           (squeeze-control-insert-player player))
390         (read-only-mode 1)))
391       (goto-char saved))))
392
393 (cl-defstruct (squeeze-player (:constructor squeeze-make-player))
394   playerindex playerid uuid ip name model isplayer displaytype canpoweroff connected power volume)
395
396 (defun squeeze-string-plistify (string start end)
397   (unless end
398     (setq end (length string)))
399   (save-match-data
400     (let (result)
401       (loop
402        (if (string-match "\\([a-z_]+\\)%3A\\([^ \n]+\\)" string start)
403            (let ((mend (match-end 0)))
404              (when (> mend end)
405                (return))
406              (push (intern (format ":%s" (substring string (match-beginning 1) (match-end 1)))) result)
407              (push (decode-coding-string
408                     (url-unhex-string (substring string (match-beginning 2) (match-end 2)))
409                     'utf-8)
410                    result)
411              (setq start mend))
412          (return)))
413       (nreverse result))))
414
415 (defun squeeze-parse-syncgroups-line (string)
416   (let ((syncgroupspos (string-match "^syncgroups " string))
417         (startpos (match-end 0)))
418     (when startpos
419       (squeeze-string-plistify string startpos (length string)))))
420
421 (defun squeeze-parse-count (string)
422   (save-match-data
423     (let ((countpos (string-match "count%3A\\([0-9]*\\)\\>" string)))
424       (if countpos
425           (string-to-number
426            (substring string (match-beginning 1) (match-end 1)))
427         (let ((kind
428                (progn (string-match "^\\([a-z]*\\) " string)
429                       (substring string (match-beginning 1) (match-end 1)))))
430           (message "no count found in %s line" kind)
431           nil)))))
432
433 (defun squeeze-parse-players-line (string)
434   (let ((count (squeeze-parse-count string))
435         (startpos (string-match "playerindex" string))
436         result endpos)
437     (when (> count 0)
438       (dotimes (i (1- count))
439         (setq endpos (progn (string-match " connected%3A[0-1] " string startpos)
440                             (match-end 0)))
441         (push (apply 'squeeze-make-player (squeeze-string-plistify string startpos endpos)) result)
442         (setq startpos endpos))
443       (push (apply 'squeeze-make-player (squeeze-string-plistify string startpos (length string))) result))
444     result))
445
446 (defcustom squeeze-artwork-directory "~/.emacs.d/squeeze/artwork/"
447   "Base directory for album and track artwork")
448
449 (defvar squeeze-albums nil)
450
451 (cl-defstruct (squeeze-album (:constructor squeeze-make-album))
452   id album artwork_track_id artist index)
453
454 (defun squeeze-parse-albums-line (string)
455   (let ((count (squeeze-parse-count string))
456         (countpos (string-match "\\_<count%3a" string))
457         (start-index-pos (progn (string-match "^albums \\([0-9]+\\)\\>" string)
458                                 (match-beginning 1)))
459         index start end)
460     (unless squeeze-albums
461       (setq squeeze-albums (make-vector count nil)))
462     (when start-index-pos
463       (setq index (string-to-number (substring string start-index-pos)))
464       (setq start (string-match "\\_<id%3a" string start-index-pos))
465       (while start
466         (setq end (string-match "\\_<\\(id%3a\\|count%3a\\)" string (1+ start)))
467         (aset squeeze-albums index
468               (apply 'squeeze-make-album
469                      :index index
470                      (squeeze-string-plistify string start end)))
471         (incf index)
472         (setq start (if (= end countpos) nil end))))))
473
474 (defun squeeze-get-albums ()
475   (squeeze-send-string "albums 0 1000 tags:lja")
476   (squeeze-accept-process-output))
477
478 (defvar squeeze-albums-mode-map
479   (let ((map (make-sparse-keymap)))
480     (define-key map (kbd "RET") 'squeeze-albums-load-album)
481     map))
482
483 (define-derived-mode squeeze-albums-mode tabulated-list-mode "SqueezeAlbums"
484   "Major mode for displaying Albums from Squeezebox Servers.\\<squeeze-albums-mode-map>"
485   (setq tabulated-list-format [("Cover" 10 nil) ("Title" 30 t)])
486   (add-hook 'tabulated-list-revert-hook 'squeeze-get-albums nil t)
487   (setq tabulated-list-entries 'squeeze-albums-tabulated-list-entries)
488   (tabbar-mode -1)
489   (setq tabulated-list-use-header-line nil)
490   (tabulated-list-init-header))
491
492 (defun squeeze-albums-artwork-callback (status artwork_track_id filename)
493   (unless status
494     (goto-char 1)
495     (let ((end (search-forward "\n\n")))
496       (delete-region 1 end)
497       (write-file (format "%s%s/%s" squeeze-artwork-directory artwork_track_id filename))
498       (kill-buffer))))
499
500 (defun squeeze-albums-tabulated-list-entry (x)
501   (let* ((ati (squeeze-album-artwork_track_id x))
502          (file (format "%s%s/cover_50x50_o.jpg" squeeze-artwork-directory ati)))
503     (unless (file-exists-p file)
504       (make-directory (format "%s%s" squeeze-artwork-directory ati) t)
505       (let ((url (format "http://%s:%s/music/%s/cover_50x50_o.jpg"
506                          squeeze-server-address squeeze-server-http-port ati)))
507         (url-queue-retrieve url 'squeeze-albums-artwork-callback
508                             (list ati "cover_50x50_o.jpg") t t)))
509     (list x (vector (propertize (format "%s" ati)
510                                 'display `(when (file-exists-p ,file)
511                                             image :type jpeg :file ,file))
512                     (squeeze-album-album x)))))
513
514 (defun squeeze-albums-tabulated-list-entries ()
515   (mapcar 'squeeze-albums-tabulated-list-entry (append squeeze-albums nil)))
516
517 (defun squeeze-complete-command-at-point ()
518   (save-excursion
519     (list (progn (backward-word) (point))
520           (progn (forward-word) (point))
521           (append
522            (mapcar 'squeeze-player-playerid squeeze-players)
523            '(;; General commands and queries
524              "login" "can" "version" "listen" "subscribe" "pref"
525              "logging" "getstring" "setsncredentials" "debug"
526              "exit" "shutdown"
527
528              ;; Player commands and queries
529              "player" "count" "id" "uuid" "name" "ip" "model" "isplayer"
530              "displaytype" "canpoweroff" "?" "signalstrength" "connected"
531              "sleep" "sync" "syncgroups" "power" "mixer" "volume" "muting"
532              "bass" "treble" "pitch" "show" "display" "linesperscreen"
533              "displaynow" "playerpref" "button" "ir" "irenable"
534              "connect" "client" "forget" "disconnect" "players"
535
536              ;; Database commands and queries
537              "rescan" "rescanprogress" "abortscan" "wipecache" "info"
538              "total" "genres" "artists" "albums" "songs" "years"
539              "musicfolder" "playlists" "tracks" "new" "rename" "delete"
540              "edit" "songinfo" "titles" "search" "pragma"
541
542              ;; Playlist commands and queries
543              "play" "stop" "pause" "mode" "time" "genre" "artist" "album"
544              "title" "duration" "remote" "current_title" "path" "playlist"
545              "add" "insert" "deleteitem" "move" "delete" "preview" "resume"
546              "save" "loadalbum" "addalbum" "loadtracks" "addtracks"
547              "insertalbum" "deletealbum" "clear" "zap" "name" "url"
548              "modified" "playlistsinfo" "index" "shuffle" "repeat"
549              "playlistcontrol"
550
551              ;; Compound queries
552              "serverstatus" "status" "displaystatus" "readdirectory"
553
554              ;; Notifications
555
556              ;; Alarm commands and queries
557              "alarm" "alarms"
558
559              ;; Plugins commands and queries
560              "favorites"
561              )))))
562
563 (defun squeeze-read-server-parameters (address port)
564   (let ((host (read-string "Host: " nil nil address))
565         (port (read-number "Port: " port)))
566     (cons host port)))
567
568 (cl-defmacro with-squeeze-parameters ((address port) &body body)
569   (declare (indent 1))
570   `(progn
571      (unless ,address (setq ,address squeeze-server-address))
572      (unless ,port (setq ,port squeeze-server-port))
573      (when current-prefix-arg
574        (let ((parameters (squeeze-read-server-parameters ,address ,port)))
575          (setq ,address (car parameters)
576                ,port (cdr parameters))))
577      ,@body))
578
579 (defun squeeze (&optional address port)
580   (interactive)
581   (with-squeeze-parameters (address port)
582     (let ((buffer (make-comint-in-buffer "squeeze" nil (cons address port))))
583       (switch-to-buffer buffer)
584       (squeeze-mode))))
585
586 (defun squeeze-control (&optional address port)
587   (interactive)
588   (with-squeeze-parameters (address port)
589     (let ((current-prefix-arg nil))
590       (squeeze address port))
591     (let ((buffer (get-buffer-create "*squeeze-control*")))
592       (switch-to-buffer buffer)
593       (squeeze-control-listen)
594       (squeeze-control-refresh)
595       (squeeze-control-display-players))))
596
597 (defun squeeze-control-reconnect (&optional address port)
598   (interactive)
599   (with-squeeze-parameters (address port)
600     (kill-buffer (get-buffer "*squeeze*"))
601     (let ((current-prefix-arg nil))
602       (squeeze-control address port))))
603
604 (defun squeeze-list-albums ()
605   (interactive)
606   (squeeze-get-albums)
607   (let ((buffer (get-buffer-create "*squeeze-albums*")))
608     (switch-to-buffer buffer)
609     (squeeze-albums-mode)
610     (tabulated-list-print)))
611
612 (defun squeeze-albums-load-album ()
613   (interactive)
614   (squeeze-send-string "%s playlistcontrol cmd:load album_id:%s"
615                        squeeze-control-current-player
616                        (squeeze-album-id (tabulated-list-get-id))))
617
618 (provide 'squeeze)