Update.
[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 (defcustom media/mplayer/capture-dir nil
36   "States where to save the dumped streams."
37   :type 'string
38   :group 'media)
39
40 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
41
42 ;; It is impossible to tell mplayer to send information every time dt
43 ;; or so, hence this mess with a timer to avoid overloading emacs with
44 ;; the processing of the information
45
46 (defvar media/mplayer/timer nil
47   "A timer to request the timing position.")
48
49 (defun media/mplayer/timing-request ()
50   (if media/mplayer/process
51       (unless media/mplayer/paused
52         (media/mplayer/write "get_time_pos\n")
53         )
54     (media/mplayer/stop-timing-requests)
55     ))
56
57 (defun media/mplayer/start-timing-requests ()
58   (when media/mplayer/timing-request-period
59     (media/mplayer/stop-timing-requests)
60     (setq media/mplayer/timer
61           (run-at-time nil
62                        media/mplayer/timing-request-period
63                        'media/mplayer/timing-request))
64     )
65   )
66
67 (defun media/mplayer/stop-timing-requests ()
68   (when media/mplayer/timer
69     (cancel-timer media/mplayer/timer)
70     (setq media/mplayer/timer nil)
71     ))
72
73 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
74
75 (setq media/mplayer/protocol-regexp
76       "^\\(AUDIO:\\|Exiting...\\|Starting\\|ANS_LENGTH\\|ANS_TIME_POSITION\\|Cache fill:\\|ICY Info:\\) *\\(.*\\)$")
77
78 (defun media/mplayer/filter-subfunctions (cmd param)
79   ;; (unless (string= cmd "A:")
80   ;; (message "cmd=%s param=%s" cmd param)
81   ;; )
82   (eval
83    (cons 'progn
84          (cdr
85           (assoc cmd
86
87                  '(
88                    ("ICY Info:"
89                     ;; (message "ICY Info \"%s\"" param)
90
91                     (if (string-match "StreamTitle='\\([^;]*\\)';" param)
92
93                         (setq media/current-song-in-stream
94                               (let ((s (match-string 1 param)))
95                                 (concat (if (string= s "")
96                                             "<no title>"
97                                           (encode-coding-string s 'latin-1)
98                                           ;; s
99                                           )
100                                         " | "
101                                         (format-time-string "%a %b %d %H:%M:%S")
102                                         )
103                                 )
104                               )
105
106                       ;; If we did not parse it properly, reset the
107                       ;; song name, and display the ICY string raw
108                       (setq media/current-song-in-stream nil)
109                       (message "ICY Info \"%s\"" param)
110                       )
111
112                     (when media/mplayer/capture-dir
113                       (let ((coding-system-for-write 'raw-text-unix))
114                         (with-temp-buffer
115                           (insert
116                            (concat media/current-song-in-stream "\n"))
117                           (write-region nil nil (concat media/mplayer/capture-dir "/log") t))))
118
119                     (if (and media/current-song-in-stream media/current-information)
120                         (media/show-current-information))
121                     )
122
123                    ;; ----------------------------------------
124
125                    ("ANS_LENGTH"
126
127                     (setq media/song-duration
128                           (string-to-number (substring param 1))))
129
130                    ;; ----------------------------------------
131
132                    ("ANS_TIME_POSITION"
133
134                     (setq media/song-current-time
135                           (string-to-number (substring param 1)))
136
137                     (when (and media/duration-to-history
138                                (< media/mplayer/cumulated-duration media/duration-to-history))
139
140                       (when media/mplayer/last-current-time
141                         (setq media/mplayer/cumulated-duration
142                               (+ media/mplayer/cumulated-duration
143                                  (- media/song-current-time media/mplayer/last-current-time))))
144
145                       (when (>= media/mplayer/cumulated-duration media/duration-to-history)
146                         (media/put-in-history)
147                         )
148
149                       (setq media/mplayer/last-current-time media/song-current-time)
150                       )
151                     )
152
153                    ;; ----------------------------------------
154
155                    ("AUDIO:"
156
157                     ;; param = "44100 Hz, 2 ch, s16le, 128.0 kbit/9.07% (ratio: 16000->176400)"
158                     (when (string-match "^\\([0-9]+\\) Hz, \\([0-9]+\\) ch.* \\([0-9.]+\\) kbit"
159                                         param)
160                       (setq media/current-information
161                             (list media/mplayer/url
162                                   (string-to-number (match-string 1 param))
163                                   (string-to-number (match-string 2 param))
164                                   (string-to-number (match-string 3 param))))
165                       )
166                     (run-hooks 'media/play-hook)
167                     )
168
169                    ;; ----------------------------------------
170
171                    ("Starting"
172                     (media/mplayer/write "get_time_length\n")
173                     (when media/mplayer/capture-dir
174                       (media/mplayer/write "capturing\n")
175                       ;; (message "Capturing stream in %s" media/mplayer/capture-dir)
176                       )
177                     )
178
179                    ;; ----------------------------------------
180
181                    ("Cache fill:"
182                     (when (string-match "(\\([0-9]+\\) bytes" param)
183                       (message "Caching stream (%dkb)"
184                                (/ (string-to-number (match-string 1 param)) 1024))))
185
186                    ;; ----------------------------------------
187
188                    ("Exiting..."
189                     (setq media/mplayer/exit-type
190                           (cdr (assoc param '(("(End of file)" . file-finished)
191                                               ("(Quit)" . quit))))
192                           media/current-information nil
193                           media/song-duration nil
194                           media/song-current-time nil)
195
196                     (when media/mplayer/process (kill-process media/mplayer/process))
197
198                     (force-mode-line-update)
199                     )
200
201                    ;; ----------------------------------------
202
203                    )
204                  )
205           )
206          )
207    )
208   )
209
210 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
211
212 (defun media/mplayer/filter (process str)
213   (setq media/mplayer/buffer (concat media/mplayer/buffer str))
214   (let ((start 0))
215     (while (and (< start (length media/mplayer/buffer))
216                 (string-match "\\(.*\\)[\n\r]+" media/mplayer/buffer start))
217       (setq start (1+ (match-end 1)))
218       (let ((line (match-string 1 media/mplayer/buffer)))
219         (when (string-match media/mplayer/protocol-regexp line)
220           (media/mplayer/filter-subfunctions (match-string 1 line) (match-string 2 line))))
221       )
222     (setq media/mplayer/buffer (substring media/mplayer/buffer start)))
223   )
224
225 (defun media/mplayer/sentinel (process str) ()
226   ;; (message "Media process got \"%s\"" (replace-regexp-in-string "\n" "" str))
227   (unless (eq (process-status media/mplayer/process) 'run)
228     (setq media/current-information nil
229           media/mplayer/process nil
230           media/song-current-time nil
231           media/song-duration nil)
232
233     (media/mplayer/stop-timing-requests)
234
235     (if (eq media/mplayer/exit-type 'file-finished)
236         (run-hooks 'media/finished-hook)
237       (run-hooks 'media/error-hook))
238
239     (force-mode-line-update))
240   )
241
242 (defun media/mplayer/write (&rest l)
243   ;;   (message "****** WROTE \"%s\"" (replace-regexp-in-string "\n" "[RETURN]" (apply 'format l)))
244   (if media/mplayer/process (process-send-string media/mplayer/process (apply 'format l))
245     (error "No mplayer process"))
246   )
247
248 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
249 ;; Player control abstract layer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
250 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
251
252 (defun media/api/init () "Called once when the media application starts"
253   (setq media/player-id "MPlayer"
254         media/mplayer/url nil
255         media/mplayer/buffer "" ;; Used as read buffer
256         media/mplayer/process nil
257         media/mplayer/exit-type nil
258         media/mplayer/paused nil
259         media/song-duration nil
260         media/song-current-time nil
261         media/mplayer/cumulated-duration 0
262         media/mplayer/last-current-time nil
263         )
264 )
265
266 (defun media/api/cleanup () "Called when killing the application's buffer"
267   (when media/mplayer/process
268     (delete-process media/mplayer/process)
269     (media/mplayer/stop-timing-requests)
270     (setq media/mplayer/process nil)))
271
272 (defun media/api/play (url) (interactive)
273   (setq media/mplayer/url url)
274
275   (when media/mplayer/process (kill-process media/mplayer/process))
276
277   ;; (if media/mplayer/process
278   ;; (media/mplayer/write (concat "loadfile "
279   ;; (replace-regexp-in-string "^file://" "" media/mplayer/url)
280   ;; "\n"))
281
282   (setq media/mplayer/process
283         (apply
284          'start-process
285          (append
286           '("mplayer" nil "mplayer" "-slave" "-quiet")
287           media/mplayer/args
288           (when (string-match  "\\(asx\\|m3u\\|pls\\|ram\\)$" media/mplayer/url)
289             (if media/mplayer/capture-dir
290                 (list "-dumpfile"
291                       (concat media/mplayer/capture-dir
292                               "/"
293                               (replace-regexp-in-string "[^a-zA-Z0-9\.]" "_" media/mplayer/url)
294                               (format-time-string "-%Y-%m-%d-%H:%M:%S"))
295                       "-capture"
296                       "-playlist"
297                       )
298               (list "-playlist"))
299             )
300           (list (replace-regexp-in-string "^file://" "" media/mplayer/url)))
301          )
302         media/mplayer/exit-type 'unknown
303         media/mplayer/paused nil
304         media/song-duration nil
305         media/song-current-time nil
306         media/mplayer/cumulated-duration 0
307         media/mplayer/last-current-time nil
308         )
309
310   (set-process-filter media/mplayer/process 'media/mplayer/filter)
311   (set-process-sentinel media/mplayer/process 'media/mplayer/sentinel)
312   (process-kill-without-query media/mplayer/process)
313   (media/mplayer/start-timing-requests)
314   (media/mplayer/write "get_time_pos\n")
315   )
316
317 (defun media/api/stop () (interactive)
318   (media/mplayer/write "quit\n")
319   )
320
321 (defun media/api/pause () (interactive)
322   (media/mplayer/write "pause\n")
323   (setq media/mplayer/paused (not media/mplayer/paused))
324   )
325
326 (defun media/api/set-volume (mode value) (interactive)
327   (if (eq mode 'absolute)
328       (media/mplayer/write "volume %s 1\n" value)
329     (if (>= value 0)
330         (media/mplayer/write "volume +%s\n" value)
331       (media/mplayer/write "volume %s\n" value))))
332
333 (defun media/api/jump-at-percent (percent) (interactive)
334   (setq media/song-current-time nil)
335   (when (< media/mplayer/cumulated-duration media/duration-to-history)
336     (setq media/mplayer/cumulated-duration 0
337           media/mplayer/last-current-time nil))
338   (media/mplayer/write "seek %s 1\n" percent)
339   (media/mplayer/write "get_time_pos\n")
340   )
341
342 (defun media/api/jump-at-time (mode time) (interactive)
343   (setq media/song-current-time nil)
344   (when (< media/mplayer/cumulated-duration media/duration-to-history)
345     (setq media/mplayer/cumulated-duration 0
346           media/mplayer/last-current-time nil))
347   (if (eq mode 'absolute)
348       (media/mplayer/write "seek %s 2\n" time)
349     (media/mplayer/write "seek %s 0\n" time))
350   (media/mplayer/write "get_time_pos\n")
351   )
352
353 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;