Added mpg123 command-line arguments
[emus.git] / emus.el
1 ;;; emus.el --- Simple mp3 player  -*- lexical-binding:t -*-
2
3 ;; Copyright (C) 2019 Tim Vaughan
4
5 ;; Author: Tim Vaughan <timv@ughan.xyz>
6 ;; Created: 8 December 2019
7 ;; Version: 1.0
8 ;; Keywords: multimedia
9 ;; Homepage: http://thelambdalab.xy/emus
10 ;; Package-Requires: ((emacs "26"))
11
12 ;; This file is not part of GNU Emacs.
13
14 ;; This program is free software: you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation, either version 3 of the License, or
17 ;; (at your option) any later version.
18
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 ;; GNU General Public License for more details.
23
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with this file.  If not, see <http://www.gnu.org/licenses/>.
26
27 ;;; Commentary:
28
29 ;; This is a simple package for playing audio from a local directory
30 ;; tree of mp3 files.  It uses the program mpg123 as its back-end.
31 ;; Currently the library is loaded completely every time emus starts.
32
33 ;;; Code:
34
35 (provide 'emus)
36
37
38 ;;; Dependencies
39 ;;
40
41 (require 'seq)
42
43
44 ;;; Customizations
45 ;;
46
47 (defgroup emus nil
48   "Simple music player for Emacs."
49   :group 'multimedia)
50
51 (defcustom emus-directory "~/Music/"
52   "Directory containing audio files for emus."
53   :type '(string))
54
55 (defcustom emus-mpg123-program "mpg123"
56   "Name of (and, optionally, path to) mpg123 binary."
57   :type '(string))
58
59 (defcustom emus-mpg123-args nil
60   "Arguments to pass to mpg123."
61   :type '(repeat string))
62
63 (defface emus-artist
64   '((((background dark)) :inherit font-lock-string-face :background "#222" :extend t)
65     (t :inherit font-lock-string-face :background "#ddd" :extend t))
66   "Face used for artist names in browser.")
67
68 (defface emus-album
69   '((((background dark)) :inherit font-lock-constant-face :background "#111" :extend t)
70     (t :inherit font-lock-constant-face :background "#eee" :extend t))
71   "Face used for album names in browser.")
72
73 (defface emus-track
74   '((t :inherit font-lock-keyword-face))
75   "Face used for track titles in browser.")
76
77 (defface emus-track-current
78   '((t :inherit font-lock-keyword-face :inverse-video t :extend t))
79   "Face used for track titles in browser.")
80
81 (defface emus-cursor
82   '((t :inherit bold))
83   "Face used for current track cursor")
84
85
86 ;;; Global variables
87
88 (defvar emus--proc-in-use nil
89   "If non-nil, disables `emus-send-cmd'.
90 Used to prevent commands from interfering with library construction.")
91
92 (defvar emus-tracks nil
93   "Emus audio library.")
94
95 (defvar emus-current-track nil
96   "Currently-selected emus track.")
97
98 (defvar emus-state 'stopped
99   "Current playback state.")
100
101 (defvar emus-continuous-playback t
102   "If non-nil, emus will automatically play the next track when the current track is finished.")
103
104 (defvar emus-current-volume 100
105   "The current playback volume.")
106
107 (defvar emus-current-progress ""
108   "String describing the progress through the current track.")
109
110 (defvar emus-progress-enabled t
111   "Current state of progress tracking.
112
113 To enable or disable progress tracking, using `emus-toggle-progress-tracking'.
114 (Changing the value of this variable will not affect anything.)")
115
116 ;;; mpg123 process
117 ;;
118
119
120 (defun emus-get-process ()
121   "Return current or new mpg123 process."
122   (let* ((emus-process-raw (get-process "emus-process"))
123          (emus-process (if emus-process-raw
124                            (if (process-live-p emus-process-raw)
125                                emus-process-raw
126                              (kill-process emus-process-raw)
127                              nil))))
128     (if emus-process
129         emus-process
130       (let ((proc
131              (make-process :name "emus-process"
132                            :command `(,emus-mpg123-program
133                                       ,@emus-mpg123-args
134                                       "-R"))))
135         (set-process-query-on-exit-flag proc nil)
136         (unless emus-progress-enabled
137           (process-send-string proc "silence\n"))
138         proc))))
139
140 (defun emus--send-cmd-raw (cmd &rest args)
141   "Send a command CMD with args ARGS to the mpg123 process.
142 This procedure does not respect `emus--proc-in-use' and thus should only
143 be used by `emus--load-library'."
144     (process-send-string (emus-get-process)
145                          (concat
146                           (seq-reduce (lambda (s1 s2) (concat s1 " " s2)) args cmd)
147                           "\n")))
148
149 (defun emus-send-cmd (cmd &rest args)
150   "Send a command CMD with args ARGS to the mpg123 process."
151   (unless emus--proc-in-use
152     (apply #'emus--send-cmd-raw cmd args)))
153
154 (defun emus-kill-process ()
155   "Kill any existing mpg123 process."
156   (let ((emus-process (get-process "emus-process")))
157     (if emus-process
158         (kill-process emus-process))
159     (setq emus-state 'stopped
160           emus--proc-in-use nil
161           emus-tracks nil)))
162
163 ;;; Library
164 ;;
165
166 (defun emus-get-audio-files ()
167   "Get all mp3 files in main emus directory."
168   (mapcar
169    #'expand-file-name
170    (directory-files-recursively emus-directory ".*\\.mp3")))
171
172 (defun emus-get-playlist-files ()
173   "Get all m3u files in the main emus music directory."
174   (mapcar
175    #'expand-file-name
176    (directory-files-recursively emus-directory ".*\\.m3u")))
177
178 (defun emus-make-track (artist album title filename &optional pos)
179   "Create an object representing an emus track.
180 ARTIST, ALBUM and TITLE are used to describe the track, FILENAME
181 refers to the mp3 file containing the track.  If non-nil, POS
182 specifies the position of the record representing this track in the
183 emus browser buffer."
184   (list artist album title filename pos))
185
186 (defun emus-track-artist (track)
187   "The artist corresponding to TRACK."
188   (elt track 0))
189
190 (defun emus-track-album (track)
191   "The album corresponding to TRACK."
192   (elt track 1))
193
194 (defun emus-track-title (track)
195   "The title of TRACK."
196   (elt track 2))
197
198 (defun emus-track-file (track)
199   "The mp3 file corresponding to TRACK."
200   (elt track 3))
201
202 (defun emus-track-browser-pos (track)
203   "The location of the browser buffer record corresponding to TRACK."
204   (elt track 4))
205
206 (defun emus-set-track-browser-pos (track pos)
207   "Set the location of the browser buffer record corresponding to TRACK to POS."
208   (setf (seq-elt track 4) pos))
209
210 (defun emus--load-library (then)
211   "Initialize the emus track library.
212 Once the library is initialized, the function THEN is called."
213   (unless emus--proc-in-use
214     (setq emus--proc-in-use t)
215     (emus--suspend-cp)
216     (setq emus-state 'stopped)
217     (setq emus-tracks nil)
218     (let ((proc (emus-get-process))
219           (tagstr "")
220           (filenames (emus-get-audio-files)))
221       (set-process-filter proc (lambda (proc string)
222                                  (setq tagstr (concat tagstr string))
223                                  (when (string-suffix-p "@P 1\n" string)
224                                    (add-to-list 'emus-tracks
225                                                 (emus--make-track-from-tagstr (car filenames)
226                                                                               tagstr))
227                                    (setq tagstr "")
228                                    (setq filenames (cdr filenames))
229                                    (if filenames
230                                        (emus--send-cmd-raw "lp" (car filenames))
231                                      (set-process-filter proc nil)
232                                      (setq emus-tracks (reverse emus-tracks))
233                                      (emus--sort-tracks)
234                                      (emus--add-tracks-from-playlist-files)
235                                      (unless emus-current-track
236                                        (setq emus-current-track (car emus-tracks)))
237                                      (funcall then)
238                                      (emus--resume-cp)
239                                      (setq emus--proc-in-use nil)))))
240       (emus--send-cmd-raw "lp" (car filenames)))))
241
242 (defun emus--dump-tracks ()
243     emus-tracks)
244
245 (defun emus--make-track-from-tagstr (filename tagstr)
246   "Parse TAGSTR to populate the fields of a track corresponding to FILENAME."
247   (let ((artist "Unknown Artist")
248         (album "Unknown Album")
249         (title filename))
250     (dolist (line (split-string tagstr "\n"))
251       (let ((found-artist (elt (split-string line "@I ID3v2.artist:") 1))
252             (found-album (elt (split-string line "@I ID3v2.album:") 1))
253             (found-title (elt (split-string line "@I ID3v2.title:") 1)))
254         (cond
255          (found-artist (setq artist found-artist))
256          (found-album (setq album found-album))
257          (found-title (setq title found-title)))))
258     (emus-make-track artist album title filename)))
259
260 (defun emus--add-tracks-from-playlist-files ()
261   (let ((tracks nil))
262     (dolist (filename (emus-get-playlist-files))
263       (let ((artist "Playlists")
264             (album (file-name-base filename))
265             (title nil)
266             (lines (split-string (with-temp-buffer
267                                    (insert-file-contents filename)
268                                    (buffer-string))
269                                  "\n")))
270         (dolist (line lines)
271           (pcase (string-trim line)
272             ((rx (: string-start
273                     (* space)
274                     "#extinf:"
275                     (* (not ",")) ","
276                     (let display-title (* any))
277                     string-end))
278              (setq title display-title))
279             ((rx (: string-start (* space) "#"))) ;skip other comments
280             ((rx (let filename (+ any)))
281              (setq tracks (cons (emus-make-track artist album (or title filename) filename)
282                                 tracks))
283              (setq title nil))))))
284     (setq emus-tracks (append emus-tracks (reverse tracks)))))
285
286 (defun emus--sort-tracks ()
287   "Sort the library tracks according to artist and album.
288 Leaves the track titles unsorted, so they will appear in the order specified
289 by the filesystem."
290   (setq emus-tracks
291         (sort emus-tracks
292               (lambda (r1 r2)
293                 (let ((artist1 (emus-track-artist r1))
294                       (artist2 (emus-track-artist r2)))
295                   (if (string= artist1 artist2)
296                       (let ((album1 (emus-track-album r1))
297                             (album2 (emus-track-album r2)))
298                         (string< album1 album2))
299                     (string< artist1 artist2)))))))
300
301 (defmacro emus--with-library (&rest body)
302   "Evaluate BODY with the library initialized."
303   `(if emus-tracks
304        (unless emus--proc-in-use ,@body)
305      (emus--load-library
306       (lambda () ,@body))))
307
308
309 ;;; Playback
310 ;;
311
312 (defun emus--suspend-cp ()
313   "Suspend continuous playback."
314   (setq emus-continuous-playback nil))
315
316 (defun emus--resume-cp ()
317   "Resume continuous playback."
318   (setq emus-continuous-playback t))
319
320 (defun emus--timestamp (seconds-total)
321   "Produce a timestamp string representation of SECONDS-TOTAL."
322   (let* ((seconds (truncate (mod seconds-total 60)))
323          (minutes (truncate (/ seconds-total 60))))
324     (format "%02d:%02d" minutes seconds)))
325
326 (defun emus-play-track (track)
327   "Set TRACK as current and start playing."
328   (emus--with-library
329    (let ((old-track emus-current-track))
330      (emus-send-cmd "l" (emus-track-file track))
331      (setq emus-state 'playing)
332      (setq emus-current-track track)
333      (setq emus-current-progress (if emus-progress-enabled "" " (progress disabled)"))
334      (set-process-filter
335       (emus-get-process)
336       (lambda (_proc string)
337         (dolist (line (string-split string "\n"))
338           (pcase line
339             ((and "@P 0"
340                   (guard emus-continuous-playback)
341                   (guard (eq emus-state 'playing)))
342              (emus-play-next))
343             ((and (guard emus-progress-enabled)
344                   (rx (: string-start
345                          "@I ICY-META: StreamTitle="
346                          (let str (+ (not ";")))
347                          ";")))
348              (message (concat "Emus: Playing stream " str)))
349             ((and (guard emus-progress-enabled)
350                   (rx (: string-start
351                          "@F "
352                          (+ digit)
353                          " "
354                          (+ digit)
355                          " "
356                          (let left-str (+ (not " ")))
357                          " "
358                          (let right-str (+ any)))))
359              (let* ((left (string-to-number left-str))
360                     (right (string-to-number right-str))
361                     (total (+ left right)))
362                (setq emus-current-progress
363                      (format " %s/%s"
364                              (emus--timestamp left)
365                              (emus--timestamp total)))))
366             ))))
367      (emus--update-track old-track)
368      (emus--update-track track)
369      (emus--resume-cp)
370      (emus-goto-current))))
371
372 (defun emus-select-track (track)
373   "Set TRACK as current, but do not start playing."
374   (emus--with-library
375    (let ((old-track emus-current-track))
376      (setq emus-state 'stopped)
377      (setq emus-current-track track)
378      (emus--update-track old-track)
379      (emus--update-track track)
380      (emus-send-cmd "o")
381      (emus--resume-cp)
382      (emus-goto-current))))
383
384 (defun emus-stop ()
385   "Stop playback of the current track."
386   (interactive)
387   (emus--with-library
388    (setq emus-state 'stopped)
389    (emus--update-track emus-current-track)
390    (emus-send-cmd "s")))
391
392 (defun emus-playpause ()
393   "Begin playback of the current track.
394 If the track is already playing, pause playback.
395 If the track is currently paused, resume playback."
396   (interactive)
397   (emus--with-library
398    (when emus-current-track
399      (if (eq emus-state 'stopped)
400          (emus-play-track emus-current-track)
401        (emus-send-cmd "p")
402        (pcase emus-state
403          ((or 'paused 'stopped) (setq emus-state 'playing))
404          ('playing (setq emus-state 'paused)))
405        (unless (eq emus-state 'paused)))
406      (emus--update-track emus-current-track))))
407
408 (defun emus-set-volume (pct)
409   "Set the playback volume to PCT %."
410   (emus--with-library
411    (setq emus-current-volume pct)
412    (emus-send-cmd "v" (number-to-string pct))))
413
414 (defun emus-volume-increase-by (delta)
415   "Increase the playback volume by DELTA %."
416   (emus-set-volume (max 0 (min 100 (+ emus-current-volume delta)))))
417
418 (defun emus-volume-up ()
419   "Increase the playback volume by 10%."
420   (interactive)
421   (emus-volume-increase-by 10))
422
423 (defun emus-volume-down ()
424   "Decrease the playback volume by 10%."
425   (interactive)
426   (emus-volume-increase-by -10))
427
428 (defun emus--play-adjacent-track (&optional prev)
429   "Play the next track in the library, or the previous if PREV is non-nil."
430   (emus--with-library
431    (let ((idx (seq-position emus-tracks emus-current-track))
432          (offset (if prev -1 +1)))
433      (if idx
434          (let ((next-track (elt emus-tracks (+ idx offset))))
435            (if next-track
436                (if (eq emus-state 'playing)
437                    (emus-play-track next-track)
438                  (emus-select-track next-track))
439              (error "Track does not exist")))
440        (error "No track selected")))))
441
442 (defun emus--play-adjacent-album (&optional prev)
443   "Play the first track of the next album in the library.
444 If PREV is non-nil, plays the last track of the previous album."
445   (emus--with-library
446    (let ((idx (seq-position emus-tracks emus-current-track)))
447      (if idx
448          (let* ((search-list (if prev
449                                  (reverse (seq-subseq emus-tracks 0 idx))
450                                (seq-subseq emus-tracks (+ idx 1))))
451                 (current-album (emus-track-album emus-current-track))
452                 (next-track (seq-some (lambda (r)
453                                         (if (string= (emus-track-album r)
454                                                      current-album)
455                                             nil
456                                           r))
457                                       search-list)))
458            (if next-track
459                (if (eq emus-state 'playing)
460                    (emus-play-track next-track)
461                  (emus-select-track next-track))
462              (error "Track does not exist")))
463        (error "No track selected")))))
464
465 (defun emus-play-next ()
466   "Play the next track in the library."
467   (interactive)
468   (emus--play-adjacent-track))
469
470 (defun emus-play-prev ()
471   "Play the previous track in the library."
472   (interactive)
473   (emus--play-adjacent-track t))
474
475 (defun emus-play-next-album ()
476   "Play the first track of the next album in the library."
477   (interactive)
478   (emus--play-adjacent-album))
479
480 (defun emus-play-prev-album ()
481   "Play the last track of the previous album in the library."
482   (interactive)
483   (emus--play-adjacent-album t))
484
485 (defun emus-jump (seconds)
486   "Jump forward in current track by SECONDS seconds."
487   (emus--with-library
488    (emus-send-cmd "jump" (format "%+ds" seconds))))
489
490 (defun emus-jump-10s-forward ()
491   "Jump 10 seconds forward in current track."
492   (interactive)
493   (emus-jump 10))
494
495 (defun emus-jump-10s-backward ()
496   "Jump 10 seconds backward in current track."
497   (interactive)
498   (emus-jump -10))
499
500 (defun emus-jump-1m-forward ()
501   "Jump 1 minute forward in current track."
502   (interactive)
503   (emus-jump 60))
504
505 (defun emus-jump-1m-backward ()
506   "Jump 1 minute backward in current track."
507   (interactive)
508   (emus-jump -60))
509
510 (defun emus-display-status ()
511   "Display the current playback status in the minibuffer."
512   (interactive)
513   (emus--with-library
514    (message
515     (concat "Emus: Volume %d%%"
516             (pcase emus-state
517               ('stopped " [Stopped]")
518               ('paused (format " [Paused%s]" emus-current-progress))
519               ('playing (format " [Playing%s]" emus-current-progress))
520               (_ ""))
521             " %s")
522     emus-current-volume
523     (if emus-current-track
524         (format "- %.30s (%.20s, %.20s)"
525                 (emus-track-title emus-current-track)
526                 (emus-track-album emus-current-track)
527                 (emus-track-artist emus-current-track))
528       ""))))
529
530 (defun emus-toggle-progress-tracking ()
531   "Enable/disable progress tracking."
532   (interactive)
533   (setq emus-progress-enabled (not emus-progress-enabled))
534   (if emus-progress-enabled
535       (progn
536         (emus-send-cmd "progress")
537         (setq emus-current-progress ""))
538     (progn
539       (emus-send-cmd "silence")
540       (setq emus-current-progress " (progress diabled)"))))
541
542
543 ;;; Browser
544 ;;
545
546 (defun emus--insert-track (track &optional prev-track first)
547   "Insert a button representing TRACK into the current buffer.
548
549 When provided, PREV-TRACK is used to determine whether to insert additional
550 headers representing the artist or the album title.
551
552 If non-nil, FIRST indicates that the track is the first in the library
553 and thus requires both artist and album headers."
554   (let* ((artist (emus-track-artist track))
555          (album (emus-track-album track))
556          (title (emus-track-title track))
557          (album-symb (intern (concat artist album)))
558          (help-str (format "mouse-1, RET: Play '%.30s' (%.20s)" title artist))
559          (field (intern album))) ;Allows easy jumping between albums with cursor.
560     (when (or prev-track first)
561       (unless (equal (emus-track-artist prev-track) artist)
562         (insert-text-button
563          (propertize artist 'face 'emus-artist)
564          'action #'emus--click-track
565          'follow-link t
566          'help-echo help-str
567          'emus-track track
568          'field field)
569         (insert (propertize "\n"
570                             'face 'emus-artist
571                             'field field)))
572       (unless (equal (emus-track-album prev-track) album)
573         (insert-text-button
574          (propertize (concat "  " album) 'face 'emus-album)
575          'action #'emus--click-track
576          'follow-link t
577          'help-echo help-str
578          'emus-track track
579          'field field)
580         (insert (propertize "\n"
581                             'face 'emus-album
582                             'field field))))
583     (emus-set-track-browser-pos track (point))
584     (let ((is-current (equal track emus-current-track)))
585       (insert-text-button
586        (concat
587         (if is-current
588             (propertize
589              (pcase emus-state
590                ('playing "->")
591                ('paused "-)")
592                ('stopped "-]"))
593              'face 'emus-cursor)
594           (propertize "  " 'face 'default))
595         (propertize (format "   %s" title)
596                     'face (if is-current
597                               'emus-track-current
598                             'emus-track)))
599        'action #'emus--click-track
600        'follow-link t
601        'help-echo help-str
602        'emus-track track
603        'invisible album-symb
604        'field field)
605       (insert (propertize "\n"
606                           'face (if is-current
607                                     'emus-track-current
608                                   'emus-track)
609                           'field field
610                           'invisible album-symb)))))
611
612 (defun emus--update-track (track)
613   "Rerender entry for TRACK in emus browser buffer.
614 Used to update browser display when `emus-current-track' and/or `emus-state' changes."
615   (let ((track-pos (emus-track-browser-pos track)))
616     (when (and (get-buffer "*emus*")
617                (emus-track-browser-pos track))
618       (with-current-buffer "*emus*"
619         (let ((inhibit-read-only t)
620               (old-point (point)))
621           (goto-char track-pos)
622           (search-forward "\n")
623           (delete-region track-pos (point))
624           (goto-char track-pos)
625           (emus--insert-track track)
626           (goto-char old-point))))))
627
628 (defun emus--render-tracks ()
629   "Render all library tracks in emus browser buffer."
630   (with-current-buffer "*emus*"
631     (let ((inhibit-read-only t)
632           (old-pos (point)))
633       (erase-buffer)
634       (goto-char (point-min))
635       (let ((prev-track nil))
636         (dolist (track emus-tracks)
637           (emus--insert-track track prev-track (not prev-track))
638           (setq prev-track track)))
639       (goto-char old-pos))))
640
641 (defun emus--click-track (button)
642   "Begin playback of track indicated by BUTTON."
643   (emus-play-track (button-get button 'emus-track))
644   (emus-display-status))
645
646 (defun emus-goto-current ()
647   "Move point to the current track in the browser buffer, if available."
648   (interactive)
649   (when (and (get-buffer "*emus*")
650              emus-current-track)
651     (with-current-buffer "*emus*"
652         (goto-char (emus-track-browser-pos emus-current-track)))))
653
654 (defun emus-browse ()
655   "Switch to *emus* audio library browser."
656   (interactive)
657   (emus--with-library
658    (pop-to-buffer-same-window "*emus*")
659    (emus-browser-mode)
660    (emus--render-tracks)
661    (emus-goto-current)))
662
663 (defun emus-refresh ()
664   "Refresh the emus library."
665   (interactive)
666   (emus-stop)
667   (setq emus-tracks nil)
668   (emus-browse))
669
670
671 ;;; Playback + status display commands
672 ;;
673
674 (defun emus-playpause-status ()
675   "Start, pause or resume playback, then display the emus status in the minibuffer."
676   (interactive)
677   (emus-playpause)
678   (emus-display-status))
679
680 (defun emus-stop-status ()
681   "Stop playback, then display the emus status in the minibuffer."
682   (interactive)
683   (emus-stop)
684   (emus-display-status))
685
686 (defun emus-volume-up-status ()
687   "Increase volume by 10%, then display the emus status in the minibuffer."
688   (interactive)
689   (emus-volume-up)
690   (emus-display-status))
691
692 (defun emus-volume-down-status ()
693   "Decrease volume by 10%, then display the emus status in the minibuffer."
694   (interactive)
695   (emus-volume-down)
696   (emus-display-status))
697
698 (defun emus-play-next-status ()
699   "Play next track, then display the emus status in the minibuffer."
700   (interactive)
701   (emus-play-next)
702   (emus-display-status))
703
704 (defun emus-play-prev-status ()
705   "Play previous track, then display the emus status in the minibuffer."
706   (interactive)
707   (emus-play-prev)
708   (emus-display-status))
709
710 (defun emus-play-next-album-status ()
711   "Play first track of next album, then display the emus status in the minibuffer."
712   (interactive)
713   (emus-play-next-album)
714   (emus-display-status))
715
716 (defun emus-play-prev-album-status ()
717   "Play last track of previous album, then display the emus status in the minibuffer."
718   (interactive)
719   (emus-play-prev-album)
720   (emus-display-status))
721
722 (defun emus-jump-10s-forward-status ()
723   "Jump 10s forward in current track, then display the emus status in the minibuffer."
724   (interactive)
725   (emus-jump-10s-forward)
726   (emus-display-status))
727
728 (defun emus-jump-10s-backward-status ()
729   "Jump 10s backward in current track, then display the emus status in the minibuffer."
730   (interactive)
731   (emus-jump-10s-backward)
732   (emus-display-status))
733
734 (defun emus-jump-1m-forward-status ()
735   "Jump 10s forward in current track, then display the emus status in the minibuffer."
736   (interactive)
737   (emus-jump-1m-forward)
738   (emus-display-status))
739
740 (defun emus-jump-1m-backward-status ()
741   "Jump 10s backward in current track, then display the emus status in the minibuffer."
742   (interactive)
743   (emus-jump-1m-backward)
744   (emus-display-status))
745
746 (defun emus-toggle-progress-status ()
747   "Toggle progress tracking, then display the emus status in the minibuffer."
748   (interactive)
749   (emus-toggle-progress-tracking)
750   (emus-display-status))
751
752 (defun emus-goto-current-status ()
753   "Move point to the current track, then display the emus status in the minibuffer."
754   (interactive)
755   (emus-goto-current)
756   (emus-display-status))
757
758 (defun emus-refresh-status ()
759   "Refresh the emus library, then display the emus status in the minibuffer."
760   (interactive)
761   (emus-stop)
762   (setq emus-tracks nil)
763   (emus-browse)
764   (emus-display-status))
765
766 (defun emus-restart-browse ()
767   "Restart the emus process, then refresh the browse window."
768   (interactive)
769   (message "Restarting mpg123.")
770   (emus-kill-process)
771   (run-at-time 0.1 nil #'emus-browse)) ;Slight delay to wait for kill signal to take effect
772
773 (defun emus-restart-status ()
774   "Restart the emus process, then display the status."
775   (interactive)
776   (message "Restarting mpg123.")
777   (emus-kill-process)
778   (run-at-time 0.1 nil #'emus-display-status)) ;Slight delay to wait for kill signal to take effect
779
780
781 (defvar emus-browser-mode-map
782   (let ((map (make-sparse-keymap)))
783     (define-key map (kbd "SPC") 'emus-playpause-status)
784     (define-key map (kbd "o") 'emus-stop-status)
785     (define-key map (kbd "+") 'emus-volume-up-status)
786     (define-key map (kbd "=") 'emus-volume-up-status)
787     (define-key map (kbd "-") 'emus-volume-down-status)
788     (define-key map (kbd "R") 'emus-refresh-status)
789     (define-key map (kbd "n") 'emus-play-next-status)
790     (define-key map (kbd "p") 'emus-play-prev-status)
791     (define-key map (kbd "N") 'emus-play-next-album-status)
792     (define-key map (kbd "P") 'emus-play-prev-album-status)
793     (define-key map (kbd ",") 'emus-jump-10s-backward-status)
794     (define-key map (kbd ".") 'emus-jump-10s-forward-status)
795     (define-key map (kbd "<") 'emus-jump-1m-backward-status)
796     (define-key map (kbd ">") 'emus-jump-1m-forward-status)
797     (define-key map (kbd "c") 'emus-goto-current-status)
798     (define-key map (kbd "#") 'emus-toggle-progress-status)
799     (define-key map (kbd "!") 'emus-restart-browse)
800     (when (fboundp 'evil-define-key*)
801       (evil-define-key* 'motion map
802         (kbd "SPC") 'emus-playpause-status
803         (kbd "o") 'emus-stop-status
804         (kbd "+") 'emus-volume-up-status
805         (kbd "=") 'emus-volume-up-status
806         (kbd "-") 'emus-volume-down-status
807         (kbd "R") 'emus-refresh-status
808         (kbd "n") 'emus-play-next-status
809         (kbd "p") 'emus-play-prev-status
810         (kbd "N") 'emus-play-next-album-status
811         (kbd "P") 'emus-play-prev-album-status
812         (kbd ",") 'emus-jump-10s-backward-status
813         (kbd ".") 'emus-jump-10s-forward-status
814         (kbd "<") 'emus-jump-1m-backward-status
815         (kbd ">") 'emus-jump-1m-forward-status
816         (kbd "c") 'emus-goto-current-status
817         (kbd "#") 'emus-toggle-progress-status
818         (kbd "!") #'emus-restart-browse))
819     map)
820   "Keymap for emus browser.")
821
822 (define-derived-mode emus-browser-mode special-mode "emus-browser"
823   "Major mode for EMUS music player file browser."
824   (setq-local buffer-invisibility-spec nil))
825
826 (when (fboundp 'evil-set-initial-state)
827   (evil-set-initial-state 'emus-browser-mode 'motion))
828
829 ;;; emus.el ends here