aboutsummaryrefslogtreecommitdiff
path: root/emms-player-mpg321-remote.el
blob: 43f2d7b503531956ec415cab88d24af813d0c1fd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
;;; emms-player-mpg321-remote.el --- play files with mpg321 -R  -*- lexical-binding: t; -*-

;; Copyright (C) 2006-2021  Free Software Foundation, Inc.

;; Author: Damien Elmes <emacs@repose.cx>
;; Keywords: emms, mp3, mpeg, multimedia

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; This file provides an emms-player which uses mpg321's remote mode
;; to play files. This is a persistent process which isn't killed each
;; time a new file is played.

;; The remote process copes graciously with errors in music files, and
;; allows you to seek in files.

;; To enable this code, add the following to your emacs configuration:

;; (require 'emms-player-mpg321-remote)
;; (push 'emms-player-mpg321-remote emms-player-list)

;;; Code:

(require 'emms)
(require 'emms-player-simple)

;; --------------------------------------------------
;; Variables and configuration
;; --------------------------------------------------

(defgroup emms-player-mpg321-remote nil
  "*EMMS player using mpg321's remote mode."
  :group 'emms-player
  :prefix "emms-player-mpg321-remote")

(defcustom emms-player-mpg321-remote-command "mpg321"
  "The command name of mpg321."
  :type  'string)

(defcustom emms-player-mpg321-remote-parameters nil
  "Extra arguments to pass to mpg321 when using remote mode
For example: (list \"-o\" \"alsa\")"
  :type  '(repeat string))

(defcustom emms-player-mpg321-remote
  (emms-player #'emms-player-mpg321-remote-start-playing
               #'emms-player-mpg321-remote-stop-playing
               #'emms-player-mpg321-remote-playable-p)
  "A player for EMMS."
  :type '(cons symbol alist))

(defvar emms-player-mpg321-remote-initial-args
  (list "--skip-printing-frames=10" "-R" "-")
  "Initial args to pass to the mpg321 process.")

(defvar emms-player-mpg321-remote-process-name "emms-player-mpg321-remote-proc"
  "The name of the mpg321 remote player process")

(defvar emms-player-mpg321-remote-ignore-stop 0
  "Number of stop messages to ignore, due to user action.")

(defmacro emms-player-mpg321-remote-add (cmd func)
  `(emms-player-set 'emms-player-mpg321-remote
                    ,cmd ,func))

(emms-player-mpg321-remote-add
 'regex (emms-player-simple-regexp "mp3" "mp2"))
(emms-player-mpg321-remote-add
 'pause 'emms-player-mpg321-remote-pause)
(emms-player-mpg321-remote-add
 'resume 'emms-player-mpg321-remote-pause)
(emms-player-mpg321-remote-add
 'seek 'emms-player-mpg321-remote-seek)

;; --------------------------------------------------
;; Process maintenence
;; --------------------------------------------------

(defun emms-player-mpg321-remote-start-process ()
  "Start a new remote process, and return the process."
  (let ((process (apply #'start-process
                        emms-player-mpg321-remote-process-name
                        nil
                        emms-player-mpg321-remote-command
                        (append emms-player-mpg321-remote-initial-args
                                emms-player-mpg321-remote-parameters))))
    (set-process-sentinel process #'emms-player-mpg321-remote-sentinel)
    (set-process-filter process #'emms-player-mpg321-remote-filter)
    process))

(defun emms-player-mpg321-remote-stop ()
  "Stop the currently playing process, if indeed there is one"
  (let ((process (emms-player-mpg321-remote-process)))
    (when process
      (kill-process process)
      (delete-process process))))

(defun emms-player-mpg321-remote-process ()
  "Return the remote process, if it exists."
  (get-process emms-player-mpg321-remote-process-name))

(defun emms-player-mpg321-remote-running-p ()
  "True if the remote process exists and is running."
  (let ((proc (emms-player-mpg321-remote-process)))
    (and proc
         (eq (process-status proc) 'run))))

(defun emms-player-mpg321-remote-sentinel (proc str)
  "Sentinel for determining the end of process"
  (ignore str)
  (when (or (eq (process-status proc) 'exit)
            (eq (process-status proc) 'signal))
    ;; reset
    (setq emms-player-mpg321-remote-ignore-stop 0)
    (message "Remote process died!")))

(defun emms-player-mpg321-remote-send (text)
  "Send TEXT to the mpg321 remote process, and add a newline."
  (let (proc)
    ;; we shouldn't be trying to send to a dead process
    (unless (emms-player-mpg321-remote-running-p)
      (emms-player-mpg321-remote-start-process))
    (setq proc  (emms-player-mpg321-remote-process))
    (process-send-string proc (concat text "\n"))))

;; --------------------------------------------------
;; Interfacing with emms
;; --------------------------------------------------

(defun emms-player-mpg321-remote-filter (proc str)
  (ignore proc)
  (let* ((data-lines (split-string str "\n" t))
         data
	 cmd)
    (dolist (line data-lines)
      (setq data (split-string line))
      (setq cmd (car data))
      (cond
       ;; stop notice
       ((and (string= cmd "@P")
             (or (string= (cadr data) "0")
		 (string= (cadr data) "3")))
        (emms-player-mpg321-remote-notify-emms))
       ;; frame notice
       ((string= cmd "@F")
        ;; even though a timer is constantly updating this variable,
        ;; updating it here will cause it to stay pretty much in sync.
        (run-hook-with-args 'emms-player-time-set-functions
                            (truncate (string-to-number (nth 3 data)))))))))

(defun emms-player-mpg321-remote-start-playing (track)
  "Start playing a song by telling the remote process to play it.
If the remote process is not running, launch it."
  (unless (emms-player-mpg321-remote-running-p)
    (emms-player-mpg321-remote-start-process))
  (emms-player-mpg321-remote-play-track track))

(defvar emms-player-ignore-stop)

(defun emms-player-mpg321-remote-notify-emms (&optional user-action)
  "Tell emms that the current song has finished.
If USER-ACTION, set `emms-player-mpg321-remote-ignore-stop' so that we
ignore the next message from mpg321."
  (if user-action
      (let ((emms-player-ignore-stop t))
        ;; so we ignore the next stop message
        (setq emms-player-mpg321-remote-ignore-stop
              (1+ emms-player-mpg321-remote-ignore-stop))
        (emms-player-stopped))
    ;; not a user action
    (if (not (zerop emms-player-mpg321-remote-ignore-stop))
        (setq emms-player-mpg321-remote-ignore-stop
              (1- emms-player-mpg321-remote-ignore-stop))
      (emms-player-stopped))))

(defun emms-player-mpg321-remote-stop-playing ()
  "Stop the current song playing."
  (emms-player-mpg321-remote-notify-emms t)
  (emms-player-mpg321-remote-send "stop"))

(defun emms-player-mpg321-remote-play-track (track)
  "Send a play command to the remote, based on TRACK."
  (emms-player-mpg321-remote-send
   (concat "load " (emms-track-get track 'name)))
  (emms-player-started 'emms-player-mpg321-remote))

(defun emms-player-mpg321-remote-playable-p (track)
  ;; use the simple definition.
  (emms-player-mpg321-playable-p track))

(defun emms-player-mpg321-remote-pause ()
  "Pause the player."
  (emms-player-mpg321-remote-send "pause"))

(defun emms-player-mpg321-remote-resume ()
  "Resume the player."
  (emms-player-mpg321-remote-send "pause"))

(defun emms-player-mpg321-remote-seek (seconds)
  "Seek forward or backward in the file."
  ;; since mpg321 only supports seeking by frames, not seconds, we
  ;; make a very rough guess as to how much a second constitutes
  (let ((frame-string (number-to-string (* 35 seconds))))
    ;; if we're not going backwards, we need to add a '+'
    (unless (eq ?- (string-to-char frame-string))
      (setq frame-string (concat "+" frame-string)))
    (emms-player-mpg321-remote-send (concat "jump " frame-string))))

(provide 'emms-player-mpg321-remote)
;;; emms-player-mpg321-remote.el ends here