Try to deal properly with quotes in the stream's song names.
[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    (cdr
76     (assoc cmd
77
78            '(
79
80              ;; ----------------------------------------
81
82              ("ICY Info:" .
83               (if (string-match "StreamTitle='\\([^;]*\\)';" param)
84                   (setq media/mplayer/current-stream-song (match-string 1 param))
85                 (message "ICY Info \"%s\"" param)))
86
87              ;; ----------------------------------------
88
89              ("ANS_LENGTH" .
90
91               (setq media/song-duration
92                     (string-to-number (substring param 1))))
93
94              ;; ----------------------------------------
95
96              ("ANS_TIME_POSITION" .
97
98               (progn
99                 (setq media/song-current-time
100                       (string-to-number (substring param 1)))
101
102                 (when (and media/duration-to-history
103                            (< media/mplayer/cumulated-duration media/duration-to-history))
104
105                   (when media/mplayer/last-current-time
106                     (setq media/mplayer/cumulated-duration
107                           (+ media/mplayer/cumulated-duration
108                              (- media/song-current-time media/mplayer/last-current-time))))
109
110                   (when (>= media/mplayer/cumulated-duration media/duration-to-history)
111                     (media/put-in-history)
112                     )
113
114                   (setq media/mplayer/last-current-time media/song-current-time)
115                   )
116
117
118                 )
119               )
120
121              ;; ----------------------------------------
122
123              ("AUDIO:" .
124
125               (progn
126                 ;; param = "44100 Hz, 2 ch, s16le, 128.0 kbit/9.07% (ratio: 16000->176400)"
127                 (when (string-match "^\\([0-9]+\\) Hz, \\([0-9]+\\) ch.* \\([0-9.]+\\) kbit"
128                                     param)
129                   (setq media/current-information
130                         (list media/mplayer/url
131                               (string-to-number (match-string 1 param))
132                               (string-to-number (match-string 2 param))
133                               (string-to-number (match-string 3 param))))
134                   )
135                 (run-hooks 'media/play-hook)
136                 ))
137
138              ;; ----------------------------------------
139
140              ("Starting" .
141               (media/mplayer/write "get_time_length\n"))
142
143              ;; ----------------------------------------
144
145              ("Cache fill:" .
146
147               (when (string-match "(\\([0-9]+\\) bytes" param)
148                 (message "Caching stream (%dkb)"
149                          (/ (string-to-number (match-string 1 param)) 1024))))
150
151              ;; ----------------------------------------
152
153              ("Exiting..." .
154
155               (progn
156                 (setq media/mplayer/exit-type
157                       (cdr (assoc param '(("(End of file)" . file-finished)
158                                           ("(Quit)" . quit))))
159                       media/current-information nil
160                       media/song-duration nil
161                       media/song-current-time nil)
162
163                 (when media/mplayer/process (kill-process media/mplayer/process))
164
165                 (force-mode-line-update)))
166
167              ;; ----------------------------------------
168
169              )
170            ))))
171
172 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
173
174 (defun media/mplayer/filter (process str)
175   (setq media/mplayer/buffer (concat media/mplayer/buffer str))
176   (let ((start 0))
177     (while (and (< start (length media/mplayer/buffer))
178                 (string-match "\\(.*\\)[\n\r]+" media/mplayer/buffer start))
179       (setq start (1+ (match-end 1)))
180       (let ((line (match-string 1 media/mplayer/buffer)))
181         (when (string-match "^\\(AUDIO:\\|Exiting...\\|Starting\\|ANS_LENGTH\\|ANS_TIME_POSITION\\|Cache fill:\\|ICY Info:\\) *\\(.*\\)$" line)
182           (media/mplayer/filter-subfunctions (match-string 1 line) (match-string 2 line))))
183
184       (when (and media/mplayer/current-stream-song media/current-information)
185         (message "Now in stream (%s) \"%s\""
186                  (current-time-string) media/mplayer/current-stream-song)
187         (setq media/mplayer/current-stream-song nil))
188       )
189     (setq media/mplayer/buffer (substring media/mplayer/buffer start)))
190   )
191
192 (defun media/mplayer/sentinel (process str) ()
193   ;; (message "Media process got \"%s\"" (replace-regexp-in-string "\n" "" str))
194   (unless (eq (process-status media/mplayer/process) 'run)
195     (setq media/current-information nil
196           media/mplayer/process nil
197           media/song-current-time nil
198           media/song-duration nil)
199
200     (media/mplayer/stop-timing-requests)
201
202     (if (eq media/mplayer/exit-type 'file-finished)
203         (run-hooks 'media/finished-hook)
204       (run-hooks 'media/error-hook))
205
206     (force-mode-line-update))
207   )
208
209 (defun media/mplayer/write (&rest l)
210   ;;   (message "****** WROTE \"%s\"" (replace-regexp-in-string "\n" "[RETURN]" (apply 'format l)))
211   (if media/mplayer/process (process-send-string media/mplayer/process (apply 'format l))
212     (error "No mplayer process"))
213   )
214
215 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
216 ;; Player control abstract layer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
217 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
218
219 (defun media/api/init () "Called once when the media application starts"
220   (setq media/player-id "MPlayer"
221         media/mplayer/url nil
222         media/mplayer/buffer "" ;; Used as read buffer
223         media/mplayer/process nil
224         media/mplayer/exit-type nil
225         media/mplayer/paused nil
226         media/song-duration nil
227         media/song-current-time nil
228         media/mplayer/current-stream-song nil
229         media/mplayer/cumulated-duration 0
230         media/mplayer/last-current-time nil
231         ))
232
233 (defun media/api/cleanup () "Called when killing the application's buffer"
234   (when media/mplayer/process
235     (delete-process media/mplayer/process)
236     (media/mplayer/stop-timing-requests)
237     (setq media/mplayer/process nil)))
238
239 (defun media/api/play (url) (interactive)
240   (setq media/mplayer/url url)
241
242   (when media/mplayer/process (kill-process media/mplayer/process))
243
244   ;; (if media/mplayer/process
245   ;; (media/mplayer/write (concat "loadfile "
246   ;; (replace-regexp-in-string "^file://" "" media/mplayer/url)
247   ;; "\n"))
248
249   (setq media/mplayer/process
250         (apply
251          'start-process
252          (append
253           '("mplayer" nil "mplayer" "-slave" "-quiet")
254           media/mplayer/args
255           (if (string-match  "\\(asx\\|m3u\\|pls\\|ram\\)$" media/mplayer/url)
256               (list "-playlist"))
257           (list (replace-regexp-in-string "^file://" "" media/mplayer/url))))
258         media/mplayer/exit-type 'unknown
259         media/mplayer/paused nil
260         media/song-duration nil
261         media/song-current-time nil
262         media/mplayer/cumulated-duration 0
263         media/mplayer/last-current-time nil
264         )
265
266   (set-process-filter media/mplayer/process 'media/mplayer/filter)
267   (set-process-sentinel media/mplayer/process 'media/mplayer/sentinel)
268   (process-kill-without-query media/mplayer/process)
269   (media/mplayer/start-timing-requests)
270   (media/mplayer/write "get_time_pos\n")
271
272   )
273
274 (defun media/api/stop () (interactive)
275   (media/mplayer/write "quit\n")
276   )
277
278 (defun media/api/pause () (interactive)
279   (media/mplayer/write "pause\n")
280   (setq media/mplayer/paused (not media/mplayer/paused))
281   )
282
283 (defun media/api/set-volume (mode value) (interactive)
284   (if (eq mode 'absolute)
285       (media/mplayer/write "volume %s 1\n" value)
286     (if (>= value 0)
287         (media/mplayer/write "volume +%s\n" value)
288       (media/mplayer/write "volume %s\n" value))))
289
290 (defun media/api/jump-at-percent (percent) (interactive)
291   (setq media/song-current-time nil)
292   (when (< media/mplayer/cumulated-duration media/duration-to-history)
293     (setq media/mplayer/cumulated-duration 0
294           media/mplayer/last-current-time nil))
295   (media/mplayer/write "seek %s 1\n" percent)
296   (media/mplayer/write "get_time_pos\n")
297   )
298
299 (defun media/api/jump-at-time (mode time) (interactive)
300   (setq media/song-current-time nil)
301   (when (< media/mplayer/cumulated-duration media/duration-to-history)
302     (setq media/mplayer/cumulated-duration 0
303           media/mplayer/last-current-time nil))
304   (if (eq mode 'absolute)
305       (media/mplayer/write "seek %s 2\n" time)
306     (media/mplayer/write "seek %s 0\n" time))
307   (media/mplayer/write "get_time_pos\n")
308   )
309
310 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;