Heavy cosmetics in media/mplayer/filter-subfunctions.
[elisp.git] / media-mplayer.el
1 ;; -*-Emacs-Lisp-*-
2
3 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4 ;; This program is free software; you can redistribute it and/or         ;;
5 ;; modify it under the terms of the GNU General Public License as        ;;
6 ;; published by the Free Software Foundation; either version 3, or (at   ;;
7 ;; your option) any later version.                                       ;;
8 ;;                                                                       ;;
9 ;; This program is distributed in the hope that it will be useful, but   ;;
10 ;; WITHOUT ANY WARRANTY; without even the implied warranty of            ;;
11 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      ;;
12 ;; General Public License for more details.                              ;;
13 ;;                                                                       ;;
14 ;; You should have received a copy of the GNU General Public License     ;;
15 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.  ;;
16 ;;                                                                       ;;
17 ;; Written by and Copyright (C) Francois Fleuret                         ;;
18 ;; Contact <francois@fleuret.org> for comments & bug reports             ;;
19 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
20
21 ;; Is it me, or the slave mode of mplayer is ugly to parse? Did I miss
22 ;; something?
23
24 (defcustom media/mplayer/args nil
25   "List of arguments for mplayer."
26   :type 'list
27   :group 'media)
28
29 (defcustom media/mplayer/timing-request-period 0.25
30   "Period for the timing requests in second(s). Larger values
31 load Emacs less. Nil means no timing."
32   :type 'float
33   :group 'media)
34
35 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
36
37 ;; It is impossible to tell mplayer to send information every time dt
38 ;; or so, hence this mess with a timer to avoid overloading emacs with
39 ;; the processing of the information
40
41 (defvar media/mplayer/timer nil
42   "A timer to request the timing position.")
43
44 (defun media/mplayer/timing-request ()
45   (if media/mplayer/process
46       (unless media/mplayer/paused
47         (media/mplayer/write "get_time_pos\n")
48         )
49     (media/mplayer/stop-timing-requests)
50     ))
51
52 (defun media/mplayer/start-timing-requests ()
53   (when media/mplayer/timing-request-period
54     (media/mplayer/stop-timing-requests)
55     (setq media/mplayer/timer
56           (run-at-time nil
57                        media/mplayer/timing-request-period
58                        'media/mplayer/timing-request))
59     )
60   )
61
62 (defun media/mplayer/stop-timing-requests ()
63   (when media/mplayer/timer
64     (cancel-timer media/mplayer/timer)
65     (setq media/mplayer/timer nil)
66     ))
67
68 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
69
70 (defun media/mplayer/filter-subfunctions (cmd param)
71   ;; (unless (string= cmd "A:")
72   ;; (message "cmd=%s param=%s" cmd param)
73   ;; )
74   (eval
75    (cons 'progn
76          (cdr
77           (assoc cmd
78
79                  '(
80
81                    ;; ----------------------------------------
82
83                    ("ICY Info:"
84                     ;; (message "ICY Info \"%s\"" param)
85
86                     (if (string-match "StreamTitle='\\([^;]*\\)';" param)
87
88                         (setq media/current-song-in-stream
89                               (let ((s (match-string 1 param)))
90                                 (concat (if (string= s "") "<no title>" s)
91                                         " | "
92                                         (format-time-string "%a %b %d %H:%M:%S")
93                                         )
94                                 )
95                               )
96
97                       (setq media/current-song-in-stream nil)
98
99                       ;; If we did not parse it properly, show it
100                       (message "ICY Info \"%s\"" param))
101
102                     (if (and media/current-song-in-stream media/current-information)
103                         (media/show-current-information))
104                     )
105
106                    ;; ----------------------------------------
107
108                    ("ANS_LENGTH"
109
110                     (setq media/song-duration
111                           (string-to-number (substring param 1))))
112
113                    ;; ----------------------------------------
114
115                    ("ANS_TIME_POSITION"
116
117                     (setq media/song-current-time
118                           (string-to-number (substring param 1)))
119
120                     (when (and media/duration-to-history
121                                (< media/mplayer/cumulated-duration media/duration-to-history))
122
123                       (when media/mplayer/last-current-time
124                         (setq media/mplayer/cumulated-duration
125                               (+ media/mplayer/cumulated-duration
126                                  (- media/song-current-time media/mplayer/last-current-time))))
127
128                       (when (>= media/mplayer/cumulated-duration media/duration-to-history)
129                         (media/put-in-history)
130                         )
131
132                       (setq media/mplayer/last-current-time media/song-current-time)
133                       )
134                     )
135
136                    ;; ----------------------------------------
137
138                    ("AUDIO:"
139
140                     ;; param = "44100 Hz, 2 ch, s16le, 128.0 kbit/9.07% (ratio: 16000->176400)"
141                     (when (string-match "^\\([0-9]+\\) Hz, \\([0-9]+\\) ch.* \\([0-9.]+\\) kbit"
142                                         param)
143                       (setq media/current-information
144                             (list media/mplayer/url
145                                   (string-to-number (match-string 1 param))
146                                   (string-to-number (match-string 2 param))
147                                   (string-to-number (match-string 3 param))))
148                       )
149                     (run-hooks 'media/play-hook)
150                     )
151
152                    ;; ----------------------------------------
153
154                    ("Starting"
155                     (media/mplayer/write "get_time_length\n"))
156
157                    ;; ----------------------------------------
158
159                    ("Cache fill:"
160                     (when (string-match "(\\([0-9]+\\) bytes" param)
161                       (message "Caching stream (%dkb)"
162                                (/ (string-to-number (match-string 1 param)) 1024))))
163
164                    ;; ----------------------------------------
165
166                    ("Exiting..."
167                     (setq media/mplayer/exit-type
168                           (cdr (assoc param '(("(End of file)" . file-finished)
169                                               ("(Quit)" . quit))))
170                           media/current-information nil
171                           media/song-duration nil
172                           media/song-current-time nil)
173
174                     (when media/mplayer/process (kill-process media/mplayer/process))
175
176                     (force-mode-line-update)
177                     )
178
179                    ;; ----------------------------------------
180
181                    )
182                  )
183           )
184          )
185    )
186   )
187
188 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
189
190 (defun media/mplayer/filter (process str)
191   (setq media/mplayer/buffer (concat media/mplayer/buffer str))
192   (let ((start 0))
193     (while (and (< start (length media/mplayer/buffer))
194                 (string-match "\\(.*\\)[\n\r]+" media/mplayer/buffer start))
195       (setq start (1+ (match-end 1)))
196       (let ((line (match-string 1 media/mplayer/buffer)))
197         (when (string-match "^\\(AUDIO:\\|Exiting...\\|Starting\\|ANS_LENGTH\\|ANS_TIME_POSITION\\|Cache fill:\\|ICY Info:\\) *\\(.*\\)$" line)
198           (media/mplayer/filter-subfunctions (match-string 1 line) (match-string 2 line))))
199       )
200     (setq media/mplayer/buffer (substring media/mplayer/buffer start)))
201   )
202
203 (defun media/mplayer/sentinel (process str) ()
204   ;; (message "Media process got \"%s\"" (replace-regexp-in-string "\n" "" str))
205   (unless (eq (process-status media/mplayer/process) 'run)
206     (setq media/current-information nil
207           media/mplayer/process nil
208           media/song-current-time nil
209           media/song-duration nil)
210
211     (media/mplayer/stop-timing-requests)
212
213     (if (eq media/mplayer/exit-type 'file-finished)
214         (run-hooks 'media/finished-hook)
215       (run-hooks 'media/error-hook))
216
217     (force-mode-line-update))
218   )
219
220 (defun media/mplayer/write (&rest l)
221   ;;   (message "****** WROTE \"%s\"" (replace-regexp-in-string "\n" "[RETURN]" (apply 'format l)))
222   (if media/mplayer/process (process-send-string media/mplayer/process (apply 'format l))
223     (error "No mplayer process"))
224   )
225
226 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
227 ;; Player control abstract layer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
228 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
229
230 (defun media/api/init () "Called once when the media application starts"
231   (setq media/player-id "MPlayer"
232         media/mplayer/url nil
233         media/mplayer/buffer "" ;; Used as read buffer
234         media/mplayer/process nil
235         media/mplayer/exit-type nil
236         media/mplayer/paused nil
237         media/song-duration nil
238         media/song-current-time nil
239         media/mplayer/cumulated-duration 0
240         media/mplayer/last-current-time nil
241         ))
242
243 (defun media/api/cleanup () "Called when killing the application's buffer"
244   (when media/mplayer/process
245     (delete-process media/mplayer/process)
246     (media/mplayer/stop-timing-requests)
247     (setq media/mplayer/process nil)))
248
249 (defun media/api/play (url) (interactive)
250   (setq media/mplayer/url url)
251
252   (when media/mplayer/process (kill-process media/mplayer/process))
253
254   ;; (if media/mplayer/process
255   ;; (media/mplayer/write (concat "loadfile "
256   ;; (replace-regexp-in-string "^file://" "" media/mplayer/url)
257   ;; "\n"))
258
259   (setq media/mplayer/process
260         (apply
261          'start-process
262          (append
263           '("mplayer" nil "mplayer" "-slave" "-quiet")
264           media/mplayer/args
265           (if (string-match  "\\(asx\\|m3u\\|pls\\|ram\\)$" media/mplayer/url)
266               (list "-playlist"))
267           (list (replace-regexp-in-string "^file://" "" media/mplayer/url))))
268         media/mplayer/exit-type 'unknown
269         media/mplayer/paused nil
270         media/song-duration nil
271         media/song-current-time nil
272         media/mplayer/cumulated-duration 0
273         media/mplayer/last-current-time nil
274         )
275
276   (set-process-filter media/mplayer/process 'media/mplayer/filter)
277   (set-process-sentinel media/mplayer/process 'media/mplayer/sentinel)
278   (process-kill-without-query media/mplayer/process)
279   (media/mplayer/start-timing-requests)
280   (media/mplayer/write "get_time_pos\n")
281
282   )
283
284 (defun media/api/stop () (interactive)
285   (media/mplayer/write "quit\n")
286   )
287
288 (defun media/api/pause () (interactive)
289   (media/mplayer/write "pause\n")
290   (setq media/mplayer/paused (not media/mplayer/paused))
291   )
292
293 (defun media/api/set-volume (mode value) (interactive)
294   (if (eq mode 'absolute)
295       (media/mplayer/write "volume %s 1\n" value)
296     (if (>= value 0)
297         (media/mplayer/write "volume +%s\n" value)
298       (media/mplayer/write "volume %s\n" value))))
299
300 (defun media/api/jump-at-percent (percent) (interactive)
301   (setq media/song-current-time nil)
302   (when (< media/mplayer/cumulated-duration media/duration-to-history)
303     (setq media/mplayer/cumulated-duration 0
304           media/mplayer/last-current-time nil))
305   (media/mplayer/write "seek %s 1\n" percent)
306   (media/mplayer/write "get_time_pos\n")
307   )
308
309 (defun media/api/jump-at-time (mode time) (interactive)
310   (setq media/song-current-time nil)
311   (when (< media/mplayer/cumulated-duration media/duration-to-history)
312     (setq media/mplayer/cumulated-duration 0
313           media/mplayer/last-current-time nil))
314   (if (eq mode 'absolute)
315       (media/mplayer/write "seek %s 2\n" time)
316     (media/mplayer/write "seek %s 0\n" time))
317   (media/mplayer/write "get_time_pos\n")
318   )
319
320 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;