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