aboutsummaryrefslogtreecommitdiff
path: root/emms-stream-info.el
blob: 59a5b6d6582eb6f5e89230f143a689db89c0d4f5 (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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
;;; emms-stream-info.el --- Show what is currently playing on a
;;; streaming audio station.

;; Copyright (C) 2004  Yoni Rabkin Katzenell <yoni-r@actcom.com>
;; This program 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 2
;; of the License, or (at your option) any later version.

;; This program 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 this program; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
;; 02110-1301 USA

;;; Commentary:
;;
;; 'emms-stream-info' establishes a TCP connection with the server and
;; sends an HTTP request string.  The server (hopefully) responds with
;; some header information describing the streaming audio channel,
;; some audio data and then the name of the song being played (usually
;; in that order).
;;
;; Some stations like WCPE [http://wcpe.org], while giving excellent
;; broadcasts do not support title streaming over MP3 or Ogg.  Using
;; this software on such stations will only result in general station
;; information and not the artist name or title of the track being
;; played.

;;; Functionality:
;;
;; Currently supports Icecast and Shoutcast servers with Ogg and MP3
;; streams.

;;; Use:
;;
;; Look at the documentation strings for the three interactive
;; functions: 'emms-stream-info-get', 'emms-stream-info-message' and
;; 'emms-stream-info-insert'.

;;; Important Notes:
;;
;; 1) This software does not parse, cache or save audio data at
;;    all. This software downloads a limited amount of data from a
;;    given streaming audio channel per call. This software is
;;    optimized to download as little as possible from a given
;;    streaming audio channel and then to immediately disconnect.
;;
;; 2) This software disregards and then discards all audio data
;;    automatically after each call.
;; 
;; 3) This software connects for a maximum of 10 seconds and then
;;    immediately disconnects. Usually the software will disconnect
;;    long before the 10 second limit is reached.
;;
;; 4) It is the responsibility of the user to read the Terms of
;;    Service of the streaming audio channel before running this
;;    software on that channel's service. Some streaming audio
;;    channels explicitly request 3rd party applications not to
;;    connect to their service. This is their prerogative. Respect it.

;; $Id: emms-stream-info.el,v 1.8 2005/07/09 11:56:00 forcer Exp $

;;; Code:

;; A higher value for 'emms-stream-info-max' this gives us a
;; correspondingly higher chance of grabbing the title information
;; from a stream but incurs a price in the additional time it takes to
;; download.
;;
;; This value is not relevant for Ogg streams since the title info in
;; Ogg streams arrives almost immediately.
;;
;; Do not set under 30000 since the typical value of 'metaint' on most
;; streaming audio servers is either 8192 or 24576
(defconst emms-stream-info-max 120000
  "Byte limit for downloads.")

(defconst emms-stream-info-timeout 10
  "Seconds to timeout connection (dead or alive).")

(defconst emms-stream-info-verbose t
  "Output real-time information about the connection.")

(defconst emms-stream-info-version
  "$Revision: 1.8 $"
  "Software version.")

(defconst emms-stream-info-char-alter-regexp "[-,'=:%+&0-9A-Za-z\.()/ ]"
  "Unified character alternative clause for regular expressions.")

(defconst emms-stream-info-shoutcast-regexp 
  (concat emms-stream-info-char-alter-regexp ".*?")
  "Regular expression for Shoutcast.")

(defconst emms-stream-info-icecast-regexp 
  (concat emms-stream-info-char-alter-regexp "+")
  "Regular expression for Icecast.")

(defconst emms-stream-info-shoutcast-title-regexp 
  (concat "StreamTitle='\\(" emms-stream-info-shoutcast-regexp "\\)';")
  "Regular expression for Shoutcast.")

;; Reference: http://www.xiph.org/ogg/vorbis/doc/framing.html
(defconst emms-stream-info-icecast-capture-pattern "Oggs\\(.*\\)BCV"
  "Regular Expression for the beggining of an Ogg bitstream page.")

;; For all servers
(defconst emms-stream-info-stream-header-regexp 
  (concat emms-stream-info-char-alter-regexp "+")
  "Regular expression for metainformation headers.")

(defconst emms-stream-info-playlist-regexp
  "\\(^http://.*\\)\\|^File.=\\(http://.*\\)"
  "Regular expression for playlist URLs.")

;; When t output debugging info
(defconst emms-stream-info-debugging nil
  "If t then emms-stream-info will spill the stream into a buffer.
Set to NIL unless you want a buffer filled with binary junk.")

(defconst emms-stream-info-debug-buffer "*emms-stream-info-debug*"
  "Buffer for debugging information.")

(defconst emms-stream-info-vocab (list "name" 
				       "genre" 
				       "pub" 
				       "metaint" 
				       "br" 
				       "bitrate" 
				       "description" 
				       "public" 
				       "audio-info")
  "List of header keys.")

(defconst emms-stream-info-format-string 
  "Now streaming:%s, %c %bKb/sec"
  "The following %-sequences are supported:

%b Bitrate
%s Song title and artist name
%c Station/Channel name and short description
%t Song title
%g Station/Channel genre
%a Artist name

Note that some stations do not supply artist and song title
information.")

(defconst emms-stream-info-format-string-notitle
  "Now streaming: %c %bKb/sec %g"
  "Some streaming audio stations/channels do not provide artist
and songtitle information. This string specifies an alternate
format for those stations.")

(defconst emms-stream-info-pls-regexp ".*\.pls"
  "Regular expression for a .pls playlist file.")

(defconst emms-stream-info-m3u-regexp ".*\.m3u"
  "Regular expression for a .m3u playlist file.")

(defvar emms-stream-info-url nil
  "Server URL.")

(defvar emms-stream-info-port nil
  "Server port.")

(defvar emms-stream-info-found nil
  "Results of our search.")

(defvar emms-stream-info-playlist-found nil
  "Results of our playlist search.")

(defvar emms-stream-info-procname "emms-stream-info-process"
  "Name of network connection process.")

(defvar emms-stream-info-downloaded 0
  "Amount of stream data downloaded.")

(defvar emms-stream-info-read-inhibit nil
  "When t do not attempt to read 'emms-stream-info-found'.")

(defvar emms-stream-info-return-hook nil
  "Activated after the disconnection from the streaming audio server.")

(defvar emms-stream-info-read-hook nil
  "Activated after the disconnection from the streaming audio
server. This hook is for integration purposes, for general user
functions use 'emms-stream-info-return-hook'.")

(defvar emms-stream-info-header-flag nil
  "Non-nil means header information has been captured.")

(defvar emms-stream-info-title-flag nil
  "Non-nil means title information has been captured.")

(defvar emms-stream-info-playlist-flag nil
  "Non-nil means playlist information has been captured.")

(defvar emms-stream-info-request-string nil
  "String sent to streaming audio server.")

(defun emms-stream-info-decompose-url (urlstr)
  "Return a vector containing the elements of the URI URLSTR."
  (let ((host nil)
	(file nil)
	(port nil)
	(protocol nil)
	(user nil)			; nil
	(pass nil)			; nil
	(refs nil)			; nil
	(attr nil)			; nil
	(full nil)
	(pos 1))
    (with-temp-buffer
      (insert urlstr)
      (goto-char (point-min))
      (if (looking-at "http")
	  (progn
	    (forward-char 4)
	    (setq protocol (buffer-substring-no-properties pos (point)))
	    (setq pos (point))))
      (skip-chars-forward "://")
      (setq pos (point))
      (skip-chars-forward "^/")
      (setq host (buffer-substring pos (point)))
      (if (string-match ":\\([0-9+]+\\)" host)
	  (setq port (string-to-number (match-string 1 host))
		host (substring host 0 (match-beginning 0))))
      (setq pos (point))
      (setq file (buffer-substring pos (point-max)))
      (setq full (buffer-substring (point-min) (point-max))))
    ;; Return in format compatible with 'url-generic-parse-url'. 
    (vector protocol user pass host port file refs attr full)))

;; This is our tiny state machine for keeping track across multiple
;; connections.
(defvar emms-stream-info-state-bv
  (make-bool-vector 3 nil)
  "State of sequential connections.
true at index 0 means output formatted message.
true at index 1 means insert formatted message.
trye at index 2 means continue to next connection.")

;; This bit is ugly and non-lispish, but asynchronous communications
;; need a state machine. Better to do it with a macro. and once
;; everything works I will too!
(defun emms-stream-info-set-message ()
  (aset emms-stream-info-state-bv 0 t))
(defun emms-stream-info-unset-message ()
  (aset emms-stream-info-state-bv 0 nil))
(defun emms-stream-info-message-p ()
  (aref emms-stream-info-state-bv 0))

(defun emms-stream-info-set-insert ()
  (aset emms-stream-info-state-bv 1 t))
(defun emms-stream-info-unset-insert ()
  (aset emms-stream-info-state-bv 1 nil))
(defun emms-stream-info-insert-p ()
  (aref emms-stream-info-state-bv 1))

(defun emms-stream-info-set-continue ()
  (aset emms-stream-info-state-bv 2 t))
(defun emms-stream-info-unset-continue ()
  (aset emms-stream-info-state-bv 2 nil))
(defun emms-stream-info-continue-p ()
  (aref emms-stream-info-state-bv 2))

(defun emms-stream-info-playlist-type (str)
  (if (stringp str)
      (cond ((string-match emms-stream-info-pls-regexp str)
	     'pls)
	    ((string-match emms-stream-info-m3u-regexp str)
	     'm3u)
	    (t nil))
    nil))

(defun emms-stream-info-format (str format-alist)
  (let ((key-list (mapcar 'car format-alist)))
    (setq key-list (mapcar 'car format-alist))
    (mapc (lambda (e)
	    (setq str 
		  (replace-regexp-in-string 
		   e 
		   (cdr (assoc e format-alist))
		   str)))
	  key-list))
  str)
    
;; Output a human readable message
(defun emms-stream-info-pretty-print (&optional string-out)
  "Output a human readable message. If STRING-OUT is non-nil, do
not output a message and only return a string."
  (let (str
	(format-string emms-stream-info-format-string)
	(format-alist
	 (list
	  (cons "%b" (or (emms-stream-info-get-key "br")
			 (emms-stream-info-get-key "bitrate")
			 ""))
	  (cons "%s" (or (emms-stream-info-get-key "songtitle") ""))
	  (cons "%c" (or (emms-stream-info-get-key "name") ""))
	  (cons "%t" (or (emms-stream-info-get-key "title") ""))
	  (cons "%g" (or (emms-stream-info-get-key "genre") ""))
	  (cons "%a" (or (emms-stream-info-get-key "artist") ""))
	  (cons "%. " ""))))		; clean untreated tags
    
    ;; Choose alternate string format if necessary
    (unless (emms-stream-info-get-key "title")
      (setq format-string emms-stream-info-format-string-notitle))

    ;; format according to the format-string
    (setq str
	  (emms-stream-info-format 
	   format-string
	   format-alist))
    
    ;; Escape rougue percent signs hiding in our string.
    (setq str (replace-regexp-in-string "%" "%%" str))

    ;; Either output a message or return a string. But only if it is
    ;; an identifiable station/channel
    (when (emms-stream-info-get-key "name")
      (if string-out
	  str
	(message str)))))

(defun emms-stream-info-pretty-print-insert ()
  "Insert the formatted output of 'emms-stream-info-get' at point."
  (insert (or (emms-stream-info-pretty-print t) "")))

(defun emms-stream-info-continue ()
  (emms-stream-info-unset-continue)
  (if emms-stream-info-playlist-found
      (emms-stream-info-get emms-stream-info-playlist-found
			    (emms-stream-info-message-p)
			    (emms-stream-info-insert-p)
			    nil)
    (error "No playlist found at URL")))

;; Useful
(defun list-to-string (l)
  "Return a STRING which is the concatenation of the elements of
L."
  (if (not l)
      nil
    (if (stringp (car l))
	(concat (car l) (list-to-string (cdr l)))
      (list-to-string (cdr l)))))

(defun emms-stream-info-get-key (key)
  "Return STRING associated with KEY."
  (unless emms-stream-info-read-inhibit
    (cdr (assoc key emms-stream-info-found))))

(defun emms-stream-info-get-keys (keys)
  "Return a list of strings associated with each key in
KEYS. KEYS should be a list of strings."
  (mapcar (lambda (e)
	    (emms-stream-info-get-key e))
	  keys))

;; BEGIN to END should typically be a segment of about 250 Bytes
;; length for Ogg streams.
(defun emms-stream-info-decode-ogg (begin end)
  "Parse Ogg stream segment from BEGIN to END."
  (let ((artist nil)
	(title nil))

    (goto-char begin)
    (re-search-forward (concat "artist=\\(" 
			       emms-stream-info-icecast-regexp 
			       "\\)") end t)
    (setq artist (match-string-no-properties 1))
    
    (goto-char begin)
    (re-search-forward (concat "title=\\(" 
			       emms-stream-info-icecast-regexp 
			       "\\)") end t)
    (setq title (match-string-no-properties 1))

    ;; ugh
    (if (or artist title)
	(list (cons "songtitle" (concat artist
					(if (and artist title)
					    " - "
					  " ")
					title))
	      (cons "artist" artist)
	      (cons "title" title))
      nil)))

;; BEGIN to END should be about 20 Bytes long
(defun emms-stream-info-decode-mp3 (begin end)
  "Parse Shoutcast/Icecast-MP3 segment from BEGIN to END."
  (let ((split nil)
	(songtitle nil)
	(artist nil)
	(title nil))

    (goto-char begin)
    (setq songtitle (buffer-substring begin end)
	  split (split-string songtitle "-"))

    (if (cdr split)
	(setq artist (car split)
	      title (list-to-string (cdr split))))

    (list (cons "songtitle" songtitle)
	  (cons "artist" artist)
	  (cons "title" title))))

(defun emms-stream-info-filter (proc str)
  "Filter function for the network process.
Argument PROC Process.
Argument STR Quanta of data."

  ;; Debugging flag dependent
  (if emms-stream-info-debugging
      (with-current-buffer emms-stream-info-debug-buffer
	(insert str)))

  (with-temp-buffer
    (setq emms-stream-info-downloaded (+ emms-stream-info-downloaded 
					 (length str)))

    ;; Insert a quanta of data.
    (insert str)

    ;; Look for headers
    (unless emms-stream-info-header-flag
      (mapcar (lambda (term)
		(goto-char (point-min))
		(if (re-search-forward 
		     (concat (regexp-opt 
			      (list "icy-" "ice-"))
			     term
			     ":\\(" 
			     emms-stream-info-stream-header-regexp
			     "\\)")
		     (point-max) t)
		    (progn
		      (add-to-list 'emms-stream-info-found 
				   (cons term 
					 (match-string-no-properties 1)))
		      (setq emms-stream-info-header-flag t))))
	      emms-stream-info-vocab))

    ;; Look for title
    (unless emms-stream-info-title-flag
      (goto-char (- (point)
		    (length str)))
      (cond ((re-search-forward 
	      emms-stream-info-icecast-capture-pattern 
	      (point-max) 
	      t)
	     (setq emms-stream-info-found 
		   (append 
		    emms-stream-info-found 
		    (emms-stream-info-decode-ogg 
		     (match-beginning 1) 
		     (match-end 1))))
	     (setq emms-stream-info-title-flag t))
	    ;; In retrospect this section mimics input_http.c from
	    ;; the Xine project only that it uses buffer searching.
	    ((re-search-forward 
	      emms-stream-info-shoutcast-title-regexp 
	      (point-max) 
	      t)
	     (setq emms-stream-info-found 
		   (append emms-stream-info-found 
			   (emms-stream-info-decode-mp3 
			    (match-beginning 1) 
			    (match-end 1))))
	     (setq emms-stream-info-title-flag t))))

    ;; Too many nested conditions
    (if (emms-stream-info-set-continue)
	(unless emms-stream-info-playlist-flag
	  (goto-char (point-min))
	  (if (re-search-forward  
	       emms-stream-info-playlist-regexp
	       (point-max) t)
	      (progn
		(setq emms-stream-info-playlist-found
		      (or (match-string-no-properties 1)
			  (match-string-no-properties 2)))
		(setq emms-stream-info-playlist-flag t))))))

  ;; Be chatty at the user
  (if emms-stream-info-verbose
      (message "Connection %s. Downloaded %d/%d bytes."
	       (process-status proc) 
	       emms-stream-info-downloaded 
	       emms-stream-info-max))

  ;; Find out if we need to kill the connection
  (if (or (> emms-stream-info-downloaded emms-stream-info-max) ; maxed out?
	  ;; Captured header and title info?
	  (and emms-stream-info-header-flag emms-stream-info-title-flag)
	  ;; Captured playlist info?
	  emms-stream-info-playlist-flag)
      (emms-stream-info-kill-process proc)))

;; Closing the connection proves to be the most difficult part of the
;; program. There is a difference in the way emacs21 vs. emacs22
;; behave.
(defun emms-stream-info-kill-process (proc)
  "Hold Emacs while trying to close the connection.
Argument PROC Process."
  (while (not (equal (process-status proc) 'closed))
    (delete-process proc))
  (if (process-filter proc)
      (set-process-filter proc nil))
  ;; Workaround Emacs 21 sentinel problems
  (when (= emacs-major-version 21)
    (emms-stream-info-after-function)))

(defun emms-stream-info-after-function ()
  "Evalutated when the connection ends."
  (setq emms-stream-info-read-inhibit nil) ; allow reading
  (run-hooks 'emms-stream-info-read-hook)
  (run-hooks 'emms-stream-info-return-hook))

(defun emms-stream-info-sentinel (proc ev)
  "Sentinel function for network process.
Argument PROC Process.
Argument EV Event string."
  ;; Workaround Emacs 21 sentinel problems
  (unless (= emacs-major-version 21)
    (emms-stream-info-after-function)))

(defun emms-stream-info-make-request-string (file)
  "Return a valid HTTP request string with FILE as a URI."
  (concat "GET "
	  (if (equal file "")
	      "/"
	    file)
	  " HTTP/1.0\r\n"
	  "User-Agent: Free software (see www.gnu.org), reads title of currently playing track (discards audio).\r\n"
	  "Icy-MetaData:1\r\n"
	  "\r\n"))

(defun emms-stream-info-parse-url (urlstring)
  "Set the global variables for connecting to the streaming audio
server at URLSTRING."
  (let* ((url (emms-stream-info-decompose-url urlstring))
	 (hostname (elt url 3))
	 (port (elt url 4))
	 (file (elt url 5))
	 (protocol (elt url 0)))

    (cond ((or (not (equal protocol "http"))
	       (equal hostname ""))
	   (error "Invalid URL"))

	  ;; eg. "http://music.station.com:8014"
	  ((and (empty-string-p file)
		port)
	   (setq emms-stream-info-port port))

	  ;; eg. "http://ogg.smgradio.com/vr96.ogg"
	  ((and (not (empty-string-p file))
		(or (equal port "")
		    (equal port nil)
		    (equal port 0)))
	   (setq emms-stream-info-port 80))

	  ;; eg. "http://audio.ibiblio.org:8010/wcpe.ogg"
	  ((and (not (empty-string-p file))
		port)
	   (setq emms-stream-info-port port))

	  (t (error "Invalid URL")))

    (setq emms-stream-info-url hostname
	  emms-stream-info-request-string 
	  (emms-stream-info-make-request-string file))))

(defun empty-string-p (str)
  "Return t if STR is equal to the empty string."
  (equal str ""))

(defun emms-stream-info-reset-state ()
  (setq emms-stream-info-downloaded 0)		 ; restart fallback
  (setq emms-stream-info-title-flag nil)	 ; forget title flag
  (setq emms-stream-info-header-flag nil)	 ; forget header flag
  (setq emms-stream-info-found nil)		 ; forget output
  (setq emms-stream-info-playlist-found nil)     ; forget playlist
  (setq emms-stream-info-playlist-flag nil)      ; forget playlist
  (setq emms-stream-info-read-inhibit t)         ; do not read output

  ;; Reset state machine
  (emms-stream-info-unset-message)
  (emms-stream-info-unset-insert)
  (emms-stream-info-unset-continue)

  ;; forget hooks
  (remove-hook 'emms-stream-info-return-hook  
	       'emms-stream-info-pretty-print)   
  (remove-hook 'emms-stream-info-return-hook 
	       'emms-stream-info-continue)
  (remove-hook 'emms-stream-info-return-hook 
	       'emms-stream-info-pretty-print-insert))

;; -------------------------------------------------------------------
;; Interactive functions
;; -------------------------------------------------------------------

(defun emms-stream-info-get (&optional urlstring say write cont)
  "Get streaming audio server header metadata and song title from stream at URL.
Argument URLSTRING Address of streaming audio server as a string.
If URLSTRING is nil then get the latest stream played via emms.
Optional argument SAY boolean.
Optional argument WRITE boolean.
Optional argument CONT boolean."
  (interactive)

  (if urlstring
      (emms-stream-info-parse-url urlstring)
    (emms-stream-info-parse-url 
     ;; possible bug, what if there is no last stream?
     (emms-stream-url emms-stream-last-stream)))

  (emms-stream-info-reset-state)

  ;; Output formatted text as a message.
  (if say
      (progn
	(add-hook 'emms-stream-info-return-hook 
		  'emms-stream-info-pretty-print)
	(emms-stream-info-set-message)))
  ;; Insert formatted text into the current buffer.
  (if write
      (progn
	(add-hook 'emms-stream-info-return-hook 
		  'emms-stream-info-pretty-print-insert)
	(emms-stream-info-set-insert)))
  ;; Continue to the next connection after this one.
  (if cont
      (progn
	(add-hook 'emms-stream-info-return-hook 
		  'emms-stream-info-continue)
	(emms-stream-info-set-continue)))

  ;; Debugging flag dependent
  (if emms-stream-info-debugging
      (progn
	(if (get-buffer emms-stream-info-debug-buffer)
	    (kill-buffer emms-stream-info-debug-buffer))
	(get-buffer-create emms-stream-info-debug-buffer)))

  ;; Open connection
  (condition-case nil
      (if (fboundp 'make-network-process)
	  (make-network-process :name emms-stream-info-procname 
				:buffer nil 
				:host emms-stream-info-url 
				:service emms-stream-info-port)
	(open-network-stream emms-stream-info-procname
			     nil
			     emms-stream-info-url
			     emms-stream-info-port))
    (error
     (emms-stream-info-reset-state)
     (message "Error connecting to streaming audio sever at %s" 
	      emms-stream-info-url)))

  (let ((proc (get-process emms-stream-info-procname)))
    (when proc

      ;; Connection timeone
      (run-at-time emms-stream-info-timeout 
		   nil 
		   'emms-stream-info-kill-process 
		   proc)

      ;; Start download
      (process-send-string emms-stream-info-procname 
			   emms-stream-info-request-string)
      (set-process-sentinel proc 
			    'emms-stream-info-sentinel)
      (set-process-filter proc 
			  'emms-stream-info-filter)
      (unless (process-sentinel proc)
	(error "No process sentinel")))))

;; Users. You can never tell what they are going to use as input.
(defun emms-stream-info-input-sanity (&optional urlstring)
  (let ((type (emms-track-type (emms-playlist-current-track))))
    (cond ((null urlstring)
	   (if (or (equal type 'playlist)
		   (equal type 'url))
	       (emms-track-name (emms-playlist-current-track))))
	  ((not (stringp urlstring))
	   (error "URL must be in string format"))
	  ((stringp url) urlstring))))

(defun emms-stream-info-message (&optional urlstring)
  "Get information from streaming audio server at URLSTRING.
Return a formatted message.
URLSTRING should be a string."
  (interactive)
  (let ((url (emms-stream-info-input-sanity urlstring)))
    (cond ((equal (emms-stream-info-playlist-type url) 'pls)
	   (emms-stream-info-get url t nil t))
	  ((equal (emms-stream-info-playlist-type url) 'm3u)
	   (emms-stream-info-get url t nil t))
	  (t (emms-stream-info-get url t)))))

;; Insertion does not work for sequential connections.
(defun emms-stream-info-insert (&optional urlstring)
  "Get information from streaming audio server at URLSTRING.
Insert a formatted message at point.
URLSTRING should be a string."
  (interactive)
  (let ((url (emms-stream-info-input-sanity urlstring)))
    (cond ((equal (emms-stream-info-playlist-type url) 'pls)
	   (emms-stream-info-get url nil t t))
	  ((equal (emms-stream-info-playlist-type url) 'm3u)
	   (emms-stream-info-get url nil t t))
	  (t (emms-stream-info-get url nil t)))))

(provide 'emms-stream-info)

;;; emms-stream-info.el ends here