emms-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emms-patches] [COMMIT] Replace lisp/emms-lastfm.el with lisp/emms-lastf


From: Yoni Rabkin
Subject: [Emms-patches] [COMMIT] Replace lisp/emms-lastfm.el with lisp/emms-lastfm-client.el.
Date: Thu, 24 Dec 2009 21:30:32 +0200

The original emms-lastfm.el doesn't work at all with the current
Last.fm API.
---
 lisp/emms-lastfm-client.el |  743 ++++++++++++++++++++++++++++++++++++++++++++
 lisp/emms-lastfm.el        |  697 -----------------------------------------
 2 files changed, 743 insertions(+), 697 deletions(-)
 create mode 100644 lisp/emms-lastfm-client.el
 delete mode 100644 lisp/emms-lastfm.el

diff --git a/lisp/emms-lastfm-client.el b/lisp/emms-lastfm-client.el
new file mode 100644
index 0000000..20ec018
--- /dev/null
+++ b/lisp/emms-lastfm-client.el
@@ -0,0 +1,743 @@
+;;; -*- show-trailing-whitespace: t -*-
+;;; emms-lastfm-client.el --- Last.FM Music API
+
+;; Copyright (C) 2009  Free Software Foundation, Inc.
+
+;; Author: Yoni Rabkin <address@hidden>
+
+;; Keywords: emms, lastfm
+
+;; EMMS 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.
+;;
+;; EMMS 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 EMMS; see the file COPYING.  If not, write to the Free
+;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+;; MA 02110-1301, USA.
+
+;;; Commentary:
+;;
+;; There are restrictions on the use of this service. Quoting from
+;; [http://www.last.fm/api/radio]: "Who can I stream radio to? Any API
+;; account can only stream radio to Last.fm's paid subscribers".
+;;
+;; We've spoken to representatives from Last.fm and arrived at the
+;; following agreement: In order to be able to use the service while
+;; preserving the essential freedoms of the GPL each client must apply
+;; for their own API key from Last.fm.
+
+;;; Installation:
+;;
+;; Here is how to get authorization from Last.fm to stream
+;; music. Thankfully this only needs to be done _once_:
+;;
+;; 1. Complete steps 1 and 2 from
+;;    [http://www.last.fm/api/authentication] to get an API key and a
+;;    secret key. Set `emms-lastfm-client-api-key' and
+;;    `emms-lastfm-client-api-secret-key' accordingly.
+;;
+;; 2. M-x emms-lastfm-client-user-authorization
+;;
+;;    If this completes successfully a browser window will open asking
+;;    for confirmation to allow this application to access the Last.fm
+;;    account. Confirm and close the browser.
+;;
+;; 3. M-x emms-lastfm-client-get-session
+;;
+;;    After this last step the permanent session key will be stored in
+;;    `emms-lastfm-client-session-key-file'. As long as this key value
+;;    is accessible the authentication process need never be repeated.
+
+;;; Use:
+;;
+;; M-x emms-lastfm-client-play-similar-artists
+;;
+;; Call the ...-play- family of interactive functions, such the above,
+;; to start streaming music.
+;;
+;; To show the currently playing track: M-x emms-lastfm-client-show
+;;
+;; To skip to the next track: M-x emms-lastfm-client-track-advance
+;;
+;; ...more Last.fm functionality to come, so stay tuned.
+
+;;; Code:
+
+(require 'md5)
+(require 'xml)
+
+(defvar emms-lastfm-client-api-key nil
+  "Key for the Last.fm API.")
+
+(defvar emms-lastfm-client-api-secret-key nil
+  "Secret key for the Last.fm API.")
+
+(defvar emms-lastfm-client-api-session-key nil
+  "Session key for the Last.fm API.")
+
+(defvar emms-lastfm-client-token nil
+  "Authorization token for API.")
+
+(defvar emms-lastfm-client-api-base-url
+  "http://ws.audioscrobbler.com/2.0/";
+  "URL for API calls.")
+
+(defvar emms-lastfm-client-session-key-file
+  (concat (file-name-as-directory emms-directory)
+         "emms-lastfm-client-sessionkey")
+  "File for storing the Last.fm API session key.")
+
+(defvar emms-lastfm-client-playlist-valid nil
+  "True if the playlist hasn't expired.")
+
+(defvar emms-lastfm-client-playlist-timer nil
+  "Playlist timer object.")
+
+(defvar emms-lastfm-client-playlist nil
+  "Latest Last.fm playlist.")
+
+(defvar emms-lastfm-client-track nil
+  "Latest Last.fm track.")
+
+(defvar emms-lastfm-client-original-next-function nil
+  "Original `-next-function' to be restored.")
+
+(defvar emms-lastfm-client-playlist-buffer-name "*Emms Last.fm*"
+  "Name for non-interactive Emms Last.fm buffer.")
+
+(defvar emms-lastfm-client-playlist-buffer nil
+  "Non-interactive Emms Last.fm buffer.")
+
+(defvar emms-lastfm-client-api-method-dict
+  '((auth-get-token    . ("auth.gettoken"
+                         emms-lastfm-client-auth-get-token-ok
+                         emms-lastfm-client-auth-get-token-failed))
+    (auth-get-session  . ("auth.getsession"
+                         emms-lastfm-client-auth-get-session-ok
+                         emms-lastfm-client-auth-get-session-failed))
+    (radio-tune        . ("radio.tune"
+                         emms-lastfm-client-radio-tune-ok
+                         emms-lastfm-client-radio-tune-failed))
+    (radio-getplaylist . ("radio.getplaylist"
+                         emms-lastfm-client-radio-getplaylist-ok
+                         emms-lastfm-client-radio-getplaylist-failed)))
+  "Mapping symbols to method calls. This is a list of cons pairs
+  where the CAR is the symbol name of the method and the CDR is a
+  list whose CAR is the method call string, CADR is the function
+  to call on a success and CADDR is the function to call on
+  failure.")
+
+;;; ------------------------------------------------------------------
+;;; API method call
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-get-method (method)
+  "Return the associated method cons for the symbol METHOD."
+  (let ((m (cdr (assoc method emms-lastfm-client-api-method-dict))))
+    (if (not m)
+       (error "method not in dictionary: %s" method)
+      m)))
+
+(defun emms-lastfm-client-get-method-name (method)
+  "Return the associated method string for the symbol METHOD."
+  (let ((this (nth 0 (emms-lastfm-client-get-method method))))
+    (if (not this)
+       (error "no name string registered for method: %s" method)
+      this)))
+
+(defun emms-lastfm-client-get-method-ok (method)
+  "Return the associated OK function for METHOD.
+
+This function is called when the method call returns
+successfully."
+  (let ((this (nth 1 (emms-lastfm-client-get-method method))))
+    (if (not this)
+       (error "no OK function registered for method: %s" method)
+      this)))
+
+(defun emms-lastfm-client-get-method-fail (method)
+  "Return the associated fail function for METHOD.
+
+This function is called when the method call returns a failure
+status message."
+  (let ((this (nth 2 (emms-lastfm-client-get-method method))))
+    (if (not this)
+       (error "no fail function registered for method: %s" method)
+      this)))
+
+(defun emms-lastfm-client-encode-arguments (arguments)
+  "Encode ARGUMENTS in UTF-8 for the Last.fm API."
+  (let ((result nil))
+    (while arguments
+      (setq result
+           (append result
+                   (list
+                    (cons
+                     (encode-coding-string (caar arguments) 'utf-8)
+                     (encode-coding-string (cdar arguments) 'utf-8)))))
+      (setq arguments (cdr arguments)))
+    result))
+
+(defun emms-lastfm-client-construct-arguments (str arguments)
+  "Return a concatenation of arguments for the URL."
+  (cond ((not arguments) str)
+       (t (emms-lastfm-client-construct-arguments
+           (concat str "&" (caar arguments) "=" (url-hexify-string (cdar 
arguments)))
+           (cdr arguments)))))
+
+(defun emms-lastfm-client-construct-method-call (method arguments)
+  "Return a complete URL method call for METHOD with ARGUMENTS.
+
+This function includes the cryptographic signature."
+  (concat emms-lastfm-client-api-base-url "?"
+         "method=" (emms-lastfm-client-get-method-name method)
+         (emms-lastfm-client-construct-arguments
+          "" arguments)
+         "&api_sig="
+         (emms-lastfm-client-construct-signature method arguments)))
+
+(defun emms-lastfm-client-construct-write-method-call (method arguments)
+  "Return a complete POST body method call for METHOD with ARGUMENTS.
+
+This function includes the cryptographic signature."
+  (concat "method=" (emms-lastfm-client-get-method-name method)
+         (emms-lastfm-client-construct-arguments
+          "" arguments)
+         "&api_sig="
+         (emms-lastfm-client-construct-signature method arguments)))
+
+;;; ------------------------------------------------------------------
+;;; Response handler
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-handle-response (method xml-response)
+  "Dispatch the handler functions of METHOD for XML-RESPONSE."
+  (let ((status (cdr (assoc 'status (nth 1 (car xml-response)))))
+       (data (cddar xml-response)))
+    (when (not status)
+      (error "error parsing status from: %s" xml-response))
+    (cond ((string= status "failed")
+          (funcall (emms-lastfm-client-get-method-fail method) data))
+         ((string= status "ok")
+          (funcall (emms-lastfm-client-get-method-ok method) data))
+         (t (error "unknown response status %s" status)))))
+
+;;; ------------------------------------------------------------------
+;;; Unathorized request token for an API account
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-construct-urt ()
+  "Return a request for an Unauthorized Request Token."
+  (let ((arguments
+        (emms-lastfm-client-encode-arguments
+         `(("api_key" . ,emms-lastfm-client-api-key)))))
+    (emms-lastfm-client-construct-method-call
+     'auth-get-token arguments)))
+
+(defun emms-lastfm-client-make-call-urt ()
+  "Make method call for Unauthorized Request Token."
+  (let* ((url-request-method "POST"))
+    (let ((response
+          (url-retrieve-synchronously
+           (emms-lastfm-client-construct-urt))))
+      (emms-lastfm-client-handle-response
+       'auth-get-token
+       (with-current-buffer response
+        (xml-parse-region (point-min) (point-max)))))))
+
+;; example response: ((lfm ((status . \"ok\")) \"\" (token nil
+;; \"31cab3398a9b46cf7231ef84d73169cf\")))
+
+;;; ------------------------------------------------------------------
+;;; Signatures
+;;; ------------------------------------------------------------------
+;;
+;; From [http://www.last.fm/api/desktopauth]:
+;;
+;; Construct your api method signatures by first ordering all the
+;; parameters sent in your call alphabetically by parameter name and
+;; concatenating them into one string using a <name><value>
+;; scheme. So for a call to auth.getSession you may have:
+;;
+;;   api_keyxxxxxxxxmethodauth.getSessiontokenxxxxxxx
+;;
+;; Ensure your parameters are utf8 encoded. Now append your secret
+;; to this string. Finally, generate an md5 hash of the resulting
+;; string. For example, for an account with a secret equal to
+;; 'mysecret', your api signature will be:
+;;
+;;   api signature = 
md5("api_keyxxxxxxxxmethodauth.getSessiontokenxxxxxxxmysecret")
+;;
+;; Where md5() is an md5 hashing operation and its argument is the
+;; string to be hashed. The hashing operation should return a
+;; 32-character hexadecimal md5 hash.
+
+(defun emms-lastfm-client-construct-lexi (arguments)
+  "Return ARGUMENTS sorted in lexicographic order."
+  (let ((lexi (sort arguments
+                   '(lambda (a b) (string< (car a) (car b)))))
+       (out ""))
+    (while lexi
+      (setq out (concat out (caar lexi) (cdar lexi)))
+      (setq lexi (cdr lexi)))
+    out))
+
+(defun emms-lastfm-client-construct-signature (method arguments)
+  "Return request signature for METHOD and ARGUMENTS."
+  (let ((complete-arguments
+        (append arguments
+                `(("method" .
+                   ,(emms-lastfm-client-get-method-name method))))))
+    (md5
+     (concat (emms-lastfm-client-construct-lexi complete-arguments)
+            emms-lastfm-client-api-secret-key))))
+
+;;; ------------------------------------------------------------------
+;;; General error handling
+;;; ------------------------------------------------------------------
+
+;; Each method call provides its own error codes, but if we don't want
+;; to code a handler for a method we call this instead:
+(defun emms-lastfm-client-default-error-handler (data)
+  "Default method failure handler."
+  (let ((errorcode (cdr (assoc 'code (nth 1 (cadr data)))))
+       (message (nth 2 (cadr data))))
+    (when (not (and errorcode message))
+      (error "failed to read errorcode or message: %s %s"
+            errorcode message))
+    (error "method call failed with code %s: %s"
+          errorcode message)))
+
+;;; ------------------------------------------------------------------
+;;; Request authorization from the user
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-ask-for-auth ()
+  "Open a Web browser for authorizing the application."
+  (when (not (and emms-lastfm-client-api-key
+                 emms-lastfm-client-token))
+    (error "API key and authorization token needed."))
+  (browse-url
+   (format "http://www.last.fm/api/auth/?api_key=%s&token=%s";
+          emms-lastfm-client-api-key
+          emms-lastfm-client-token)))
+
+;;; ------------------------------------------------------------------
+;;; Parse XSPF
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-xspf-header (data)
+  "Return an alist representing the XSPF header of DATA."
+  (let (out
+       (orig data))
+    (setq data (cadr data))
+    (while data
+      (when (and (car data)
+                (listp (car data))
+                (= (length (car data)) 3))
+       (setq out (append out (list (cons (nth 0 (car data))
+                                         (nth 2 (car data)))))))
+      (setq data (cdr data)))
+    (if (not out)
+       (error "failed to parse XSPF header from: %s" orig)
+      out)))
+
+(defun emms-lastfm-client-xspf-tracklist (data)
+  "Return the start of the track-list in DATE."
+  (nthcdr 3 (nth 11 (cadr data))))
+
+(defun emms-lastfm-client-xspf-header-date (header-alist)
+  "Return the date parameter from HEADER-ALIST."
+  (let ((out (cdr (assoc 'date header-alist))))
+    (if (not out)
+       (error "could not read date from header alist: %s"
+              header-alist)
+      out)))
+
+(defun emms-lastfm-client-xspf-header-expiry (header-alist)
+  "Return the expiry parameter from HEADER-ALIST."
+  (let ((out (cdr (assoc 'link header-alist))))
+    (if (not out)
+       (error "could not read expiry from header alist: %s"
+              header-alist)
+      out)))
+
+(defun emms-lastfm-client-xspf-header-creator (header-alist)
+  "Return the creator parameter from HEADER-ALIST."
+  (let ((out (cdr (assoc 'creator header-alist))))
+    (if (not out)
+       (error "could not read creator from header alist: %s"
+              header-alist)
+      out)))
+
+(defun emms-lastfm-client-xspf-playlist (data)
+  "Return the playlist from the XSPF DATA."
+  (let ((playlist (car (nthcdr 11 data))))
+    (if (not playlist)
+       (error "could not read playlist from: %s" data)
+      playlist)))
+
+(defun emms-lastfm-client-xspf-get (node track)
+  "Return data associated with NODE in TRACK."
+  (let ((result nil))
+    (while track
+      (when (consp track)
+       (let ((this (car track)))
+         (when (and (consp this)
+                    (= (length this) 3)
+                    (symbolp (nth 0 this))
+                    (stringp (nth 2 this))
+                    (equal (nth 0 this) node))
+           (setq result (nth 2 this)))))
+      (setq track (cdr track)))
+    (if (not result)
+       nil
+      result)))
+
+;;; ------------------------------------------------------------------
+;;; Timers
+;;; ------------------------------------------------------------------
+
+;; timed playlist invalidation is a part of the Last.fm API
+(defun emms-lastfm-client-set-timer (header)
+  "Start timer countdown to playlist invalidation"
+  (when (not header)
+    (error "can't set timer with no header data"))
+  (let ((expiry (parse-integer
+                (emms-lastfm-client-xspf-header-expiry header))))
+    (setq emms-lastfm-client-playlist-valid t)
+    (setq emms-lastfm-client-playlist-timer
+         (run-at-time
+          expiry nil
+          '(lambda () (setq emms-lastfm-client-playlist-valid
+                            nil))))))
+
+;;; ------------------------------------------------------------------
+;;; Player
+;;; ------------------------------------------------------------------
+
+;; this should return `nil' to the track-manager when the playlist has
+;; been exhausted
+(defun emms-lastfm-client-consume-next-track ()
+  "Pop and return the next track from the playlist or nil."
+  (when emms-lastfm-client-playlist
+    (if emms-lastfm-client-playlist-valid
+       (let ((track (car emms-lastfm-client-playlist)))
+         ;; we can only request each track once so we pop it off the
+         ;; playlist
+         (setq emms-lastfm-client-playlist
+               (if (stringp (cdr emms-lastfm-client-playlist))
+                   (cddr emms-lastfm-client-playlist)
+                 (cdr emms-lastfm-client-playlist)))
+         track)
+      (error "playlist invalid"))))
+
+(defun emms-lastfm-client-set-lastfm-playlist-buffer ()
+  (when (not (buffer-live-p emms-lastfm-client-playlist-buffer))
+    (setq emms-lastfm-client-playlist-buffer
+         (emms-playlist-new
+          emms-lastfm-client-playlist-buffer-name))
+    (setq emms-playlist-buffer emms-lastfm-client-playlist-buffer)))
+
+(defun emms-lastfm-client-load-next-track ()
+  (with-current-buffer emms-lastfm-client-playlist-buffer
+    (emms-playlist-clear)
+    (if emms-lastfm-client-playlist
+       (let ((track (emms-lastfm-client-consume-next-track)))
+         (setq emms-lastfm-client-track track)
+         (when emms-player-playing-p
+           (emms-stop))
+         (emms-play-url
+          (emms-lastfm-client-xspf-get 'location track)))
+      (emms-lastfm-client-make-call-radio-getplaylist)
+      (emms-lastfm-client-load-next-track))))
+
+;; call this `-track-advance' to avoid confusion with Emms'
+;; `-next-track-' mechanism
+(defun emms-lastfm-client-track-advance ()
+  (interactive)
+  (when (equal emms-playlist-buffer
+              emms-lastfm-client-playlist-buffer)
+    (emms-lastfm-client-load-next-track)))
+
+(defun emms-lastfm-client-play-playlist ()
+  "Entry point to play tracks from Last.fm."
+  (emms-lastfm-client-set-lastfm-playlist-buffer)
+  (add-hook 'emms-player-finished-hook
+           'emms-lastfm-client-track-advance)
+  (emms-lastfm-client-track-advance))
+
+;; stolen from Tassilo Horn's original emms-lastfm.el
+(defun emms-lastfm-client-read-artist ()
+  "Read an artist name from the user."
+  (let ((artists nil))
+    (when (boundp 'emms-cache-db)
+      (maphash
+       #'(lambda (file track)
+          (let ((artist (emms-track-get track 'info-artist)))
+            (when artist
+              (add-to-list 'artists artist))))
+       emms-cache-db))
+    (if artists
+       (emms-completing-read "Artist: " artists)
+      (read-string "Artist: "))))
+
+(defun emms-lastfm-client-play-similar-artists (artist)
+  "Play a Last.fm station with music similar to ARTIST."
+  (interactive (list (emms-lastfm-client-read-artist)))
+  (when (not (stringp artist))
+    (error "not a string: %s" artist))
+  (emms-lastfm-client-check-session-key)
+  (emms-lastfm-client-make-call-radio-tune
+   (format "lastfm://artist/%s/similarartists" artist))
+  (emms-lastfm-client-make-call-radio-getplaylist)
+  (emms-lastfm-client-play-playlist))
+
+;;; ------------------------------------------------------------------
+;;; Information
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-convert-track (track)
+  "Convert a Last.fm track to an Emms track."
+  (let ((emms-track (emms-dictionary '*track*)))
+    (emms-track-set emms-track 'name
+                   (emms-lastfm-client-xspf-get 'location track))
+    (emms-track-set emms-track 'info-artist
+                   (emms-lastfm-client-xspf-get 'creator track))
+    (emms-track-set emms-track 'info-title
+                   (emms-lastfm-client-xspf-get 'title track))
+    (emms-track-set emms-track 'info-album
+                   (emms-lastfm-client-xspf-get 'album track))
+    (emms-track-set emms-track 'info-playing-time
+                   (/
+                    (parse-integer
+                     (emms-lastfm-client-xspf-get 'duration track))
+                    1000))
+    emms-track))
+
+(defun emms-lastfm-client-show-track (track)
+  "Return description of TRACK."
+  (decode-coding-string
+   (format emms-show-format
+          (emms-track-description
+           (emms-lastfm-client-convert-track track)))
+   'utf-8))
+
+(defun emms-lastfm-client-show ()
+  "Display a description of the current track."
+  (interactive)
+  (if emms-player-playing-p
+      (message
+       (emms-lastfm-client-show-track emms-lastfm-client-track))
+    nil))
+
+;;; ------------------------------------------------------------------
+;;; Desktop application authorization [http://www.last.fm/api/desktopauth]
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-user-authorization ()
+  "Ask user to authorize the application."
+  (interactive)
+  (emms-lastfm-client-make-call-urt)
+  (emms-lastfm-client-ask-for-auth))
+
+(defun emms-lastfm-client-get-session ()
+  "Retrieve and store session key."
+  (interactive)
+  (emms-lastfm-client-make-call-get-session)
+  (emms-lastfm-client-save-session-key
+   emms-lastfm-client-api-session-key))
+
+;;; ------------------------------------------------------------------
+;;; method: auth.getToken [http://www.last.fm/api/show?service=265]
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-auth-get-token-ok (data)
+  "Function called when auth.getToken succeeds."
+  (setq emms-lastfm-client-token
+       (nth 2 (cadr data)))
+  (if (or (not emms-lastfm-client-token)
+         (not (= (length emms-lastfm-client-token) 32)))
+      (error "could not read token from response %s" data)
+    (message "Emms Last.FM auth.getToken method call success.")))
+
+(defun emms-lastfm-client-auth-get-token-failed (data)
+  "Function called when auth.getToken fails."
+  (emms-lastfm-client-default-error-handler data))
+
+;;; ------------------------------------------------------------------
+;;; method: auth.getSession [http://www.last.fm/api/show?service=125]
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-construct-get-session ()
+  "Return an auth.getSession request string."
+  (let ((arguments
+        (emms-lastfm-client-encode-arguments
+         `(("token"   . ,emms-lastfm-client-token)
+           ("api_key" . ,emms-lastfm-client-api-key)))))
+    (emms-lastfm-client-construct-method-call
+     'auth-get-session arguments)))
+
+(defun emms-lastfm-client-make-call-get-session ()
+  "Make auth.getSession call."
+  (let* ((url-request-method "POST"))
+    (let ((response
+          (url-retrieve-synchronously
+           (emms-lastfm-client-construct-get-session))))
+      (emms-lastfm-client-handle-response
+       'auth-get-session
+       (with-current-buffer response
+        (xml-parse-region (point-min) (point-max)))))))
+
+(defun emms-lastfm-client-save-session-key (key)
+  "Store KEY."
+  (let ((buffer (find-file-noselect
+                emms-lastfm-client-session-key-file)))
+    (set-buffer buffer)
+    (erase-buffer)
+    (insert key)
+    (save-buffer)
+    (kill-buffer buffer)))
+
+(defun emms-lastfm-client-load-session-key ()
+  "Return stored session key."
+  (let ((file (expand-file-name emms-lastfm-client-session-key-file)))
+    (setq emms-lastfm-client-api-session-key
+         (if (file-readable-p file)
+             (with-temp-buffer
+               (emms-insert-file-contents file)
+               (goto-char (point-min))
+               (buffer-substring-no-properties
+                (point) (point-at-eol)))
+           nil))))
+
+(defun emms-lastfm-client-check-session-key ()
+  "Signal an error condition if there is no session key."
+  (if emms-lastfm-client-api-session-key
+      emms-lastfm-client-api-session-key
+    (if (emms-lastfm-client-load-session-key)
+       emms-lastfm-client-api-session-key
+      (error "no session key for API access"))))
+
+(defun emms-lastfm-client-auth-get-session-ok (data)
+  "Function called on DATA if auth.getSession succeeds."
+  (let ((session-key (nth 2 (nth 5 (cadr data)))))
+    (cond (session-key
+          (setq emms-lastfm-client-api-session-key
+                session-key)
+          (message "Emms Last.fm session key retrieval successful"
+                   session-key))
+         (t (error "failed to parse session key data %s" data)))))
+
+(defun emms-lastfm-client-auth-get-session-failed (data)
+  "Function called on DATA if auth.getSession fails."
+  (emms-lastfm-client-default-error-handler data))
+
+;;; ------------------------------------------------------------------
+;;; method: radio.tune [http://www.last.fm/api/show?service=160]
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-construct-radio-tune (station)
+  "Return a request to tune to STATION."
+  (let ((arguments
+        (emms-lastfm-client-encode-arguments
+         `(("sk"   . ,emms-lastfm-client-api-session-key)
+           ("station" . ,station)
+           ("api_key" . ,emms-lastfm-client-api-key)))))
+    (emms-lastfm-client-construct-write-method-call
+     'radio-tune arguments)))
+
+(defun emms-lastfm-client-make-call-radio-tune (station)
+  "Make call to tune to STATION."
+  (let ((url-request-method "POST")
+       (url-request-extra-headers
+        `(("Content-type" . "application/x-www-form-urlencoded")))
+       (url-request-data
+        (emms-lastfm-client-construct-radio-tune station)))
+    (let ((response
+          (url-retrieve-synchronously
+           emms-lastfm-client-api-base-url)))
+      (emms-lastfm-client-handle-response
+       'radio-tune
+       (with-current-buffer response
+        (xml-parse-region (point-min) (point-max)))))))
+
+(defun emms-lastfm-client-radio-tune-failed (data)
+  "Function called on DATA when tuning fails."
+  (emms-lastfm-client-default-error-handler data))
+
+(defun emms-lastfm-client-radio-tune-ok (data)
+  "Set the current radio station according to DATA."
+  (let ((response (cdr (cadr data)))
+       data)
+    (while response
+      (when (and (listp (car response))
+                (car response)
+                (= (length (car response)) 3))
+       (add-to-list 'data (cons (caar response)
+                                (caddr (car response)))))
+      (setq response (cdr response)))
+    (when (not data)
+      (error "could not parse station information %s" data))
+    (setq emms-lastfm-client-tuned-station-alist data)))
+
+;;; ------------------------------------------------------------------
+;;; method: radio.getPlaylist [http://www.last.fm/api/show?service=256]
+;;; ------------------------------------------------------------------
+
+(defun emms-lastfm-client-construct-radio-getplaylist ()
+  "Return a request for a playlist from the tuned station."
+  (let ((arguments
+        (emms-lastfm-client-encode-arguments
+         `(("sk"   . ,emms-lastfm-client-api-session-key)
+           ("api_key" . ,emms-lastfm-client-api-key)))))
+    (emms-lastfm-client-construct-write-method-call
+     'radio-getplaylist arguments)))
+
+(defun emms-lastfm-client-make-call-radio-getplaylist ()
+  "Make call for playlist from the tuned station."
+  (let ((url-request-method "POST")
+       (url-request-extra-headers
+        `(("Content-type" . "application/x-www-form-urlencoded")))
+       (url-request-data
+        (emms-lastfm-client-construct-radio-getplaylist)))
+    (let ((response
+          (url-retrieve-synchronously
+           emms-lastfm-client-api-base-url)))
+      (emms-lastfm-client-handle-response
+       'radio-getplaylist
+       (with-current-buffer response
+        (xml-parse-region (point-min) (point-max)))))))
+
+(defun emms-lastfm-client-radio-getplaylist-failed (data)
+  "Function called on DATA when retrieving a playlist fails."
+  'stub-needs-to-handle-playlist-issues
+  (emms-lastfm-client-default-error-handler data))
+
+(defun emms-lastfm-client-list-filter (l)
+  "Remove strings from the roots of list L."
+  (let (acc)
+    (while l
+      (when (listp (car l))
+       (push (car l) acc))
+      (setq l (cdr l)))
+    (reverse acc)))
+
+(defun emms-lastfm-client-radio-getplaylist-ok (data)
+  "Function called on DATA when retrieving a playlist succeeds."
+  (let ((header (emms-lastfm-client-xspf-header data))
+       (tracklist (emms-lastfm-client-xspf-tracklist data)))
+    (emms-lastfm-client-set-timer header)
+    (setq emms-lastfm-client-playlist
+         (emms-lastfm-client-list-filter tracklist))))
+
+(provide 'emms-lastfm-client)
+
+;;; emms-lastfm-client.el ends here
diff --git a/lisp/emms-lastfm.el b/lisp/emms-lastfm.el
deleted file mode 100644
index a58e13a..0000000
--- a/lisp/emms-lastfm.el
+++ /dev/null
@@ -1,697 +0,0 @@
-;;; emms-lastfm.el --- add your listened songs to your profile at last.fm
-
-;; Copyright (C) 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
-
-;; Author: Tassilo Horn <address@hidden>
-
-;; Keywords: emms, mp3, mpeg, multimedia
-
-;; This file is part of EMMS.
-
-;; EMMS 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.
-
-;; EMMS 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
-;; EMMS; see the file COPYING.  If not, write to the Free Software Foundation,
-;; Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-;;; Commentary:
-
-;; This code sends information about what music you are playing to last.fm.
-;; See <URL:http://www.last.fm> and
-;; <URL:http://www.audioscrobbler.net/wiki/Protocol1.1>.
-
-;;; Sample configuration:
-
-;; (setq emms-lastfm-username "my-user-name"
-;;       emms-lastfm-password "very-secret!")
-
-;;; Usage:
-
-;; To activate the last.fm emms plugin, run:
-;;   `M-x emms-lastfm-enable'
-
-;; Now all music you listen to will be submitted to Last.fm to enhance your
-;; profile.
-
-;; To deactivate the last.fm emms plugin, run:
-;;   `M-x emms-lastfm-disable'
-
-;; Beside submitting the tracks you listen to, you can also listen to Last.fm
-;; radio. Simply copy the lastfm:// URL and run & paste:
-;;   `M-x emms-lastfm-radio RET lastfm://artist/Britney Spears/fans'
-;; (Of course you don't need to use _this_ URL. :-))
-
-;; You can also insert Last.fm streams into playlists (or use
-;; emms-streams.el to listen to them) by activating the player as
-;; follows.
-;;   (add-to-list 'emms-player-list 'emms-player-lastfm-radio)
-;; To insert a Last.fm stream into a playlist, do
-;;   (emms-insert-lastfm "lastfm://rest-of-url")
-
-;; There are some functions for conveniently playing the Similar
-;; Artists, Fan Radio, and the Global Tag Radio. Here you only need to
-;; enter the band's name (for the first two) or the tag.
-;;   `M-x emms-play-lastfm-similar-artists RET Britney Spears'
-;;   `M-x emms-play-lastfm-artist-fan RET Modest Mouse'
-;;   `M-x emms-play-lastfm-global-tag RET pop'
-
-;; When you're listening to a Last.fm radio station you have the possibility to
-;; give feedback to them. If you like the current song, type
-;;   `M-x emms-lastfm-radio-love'.
-;; If it's not that good, or it just happens to not fit to your actual mood,
-;; type
-;;   `M-x emms-lastfm-radio-skip'
-;; and this song will be skipped.
-;; If you really hate that song and you never want to hear it again, ban it by
-;; typing
-;;   `M-x emms-lastfm-radio-ban'.
-
-;;; TODO
-;;
-;; - Get the last.fm radio stuff right again.  Currently the rating stuff seems
-;;   to be broken.  There seems to be no official API, so one needs to look
-;;   into the sources of the official client which can be found at
-;;   http://www.audioscrobbler.net/development/client/.
-
-;; -----------------------------------------------------------------------
-
-(require 'url)
-(require 'emms)
-(require 'emms-mode-line)
-(require 'emms-playing-time)
-(require 'emms-source-file)
-(require 'emms-url)
-
-;;; Variables
-
-(defgroup emms-lastfm nil
-  "Interaction with the services offered by http://www.last.fm.";
-  :prefix "emms-lastfm-"
-  :group 'emms)
-
-(defcustom emms-lastfm-username ""
-  "Your last.fm username"
-  :type 'string
-  :group 'emms-lastfm)
-
-(defcustom emms-lastfm-password ""
-  "Your last.fm password"
-  :type 'string
-  :group 'emms-lastfm)
-
-(defcustom emms-lastfm-submission-verbose-p nil
-  "If non-nil, display a message every time we submit a track to Last.fm."
-  :type 'boolean
-  :group 'emms-lastfm)
-
-(defcustom emms-lastfm-submit-track-types '(file)
-  "Specify what types of tracks to submit to Last.fm.
-The default is to only submit files.
-
-To submit every track to Last.fm, set this to t.
-
-Note that it is not very meaningful to submit playlists,
-streamlists, or Last.fm streams to Last.fm."
-  :type '(choice (const :tag "All" t)
-                 (set :tag "Types"
-                      (const :tag "Files" file)
-                      (const :tag "URLs" url)
-                      (const :tag "Playlists" playlist)
-                      (const :tag "Streamlists" streamlist)
-                      (const :tag "Last.fm streams" lastfm)))
-  :group 'emms-lastfm)
-
-(defconst emms-lastfm-server "http://post.audioscrobbler.com/";
-  "The last.fm server responsible for the handshaking
-procedure. Only for internal use.")
-(defconst emms-lastfm-client-id "ems"
-  "The client ID of EMMS. Don't change it!")
-(defconst emms-lastfm-client-version 0.2
-  "The version registered at last.fm. Don't change it!")
-(defconst emms-lastfm-protocol-version 1.2
-  "The version of the supported last.fm protocol.  Don't change it.")
-
-;; used internally
-(defvar emms-lastfm-process nil "-- only used internally --")
-(defvar emms-lastfm-session-id nil "-- only used internally --")
-(defvar emms-lastfm-now-playing-url nil "-- only used internally --")
-(defvar emms-lastfm-submit-url nil "-- only used internally --")
-(defvar emms-lastfm-current-track nil "-- only used internally --")
-(defvar emms-lastfm-timer nil "-- only used internally --")
-(defvar emms-lastfm-current-track-starting-time-string nil "-- only used 
internally --")
-
-;;; Scrobbling
-
-(defun emms-lastfm-new-track-function ()
-  "This function should run whenever a new track starts (or a
-paused track resumes) and sets the track submission timer."
-  (setq emms-lastfm-current-track
-        (emms-playlist-current-selected-track))
-  (setq emms-lastfm-current-track-starting-time-string
-        (emms-lastfm-current-unix-time-string))
-  ;; Tracks should be submitted, if they played 240 secs or half of their
-  ;; length, whichever comes first.
-  (let ((secs (emms-track-get emms-lastfm-current-track 'info-playing-time))
-        (type (emms-track-type emms-lastfm-current-track)))
-    (when (and secs
-               (or (eq emms-lastfm-submit-track-types t)
-                   (and (listp emms-lastfm-submit-track-types)
-                        (memq type emms-lastfm-submit-track-types))))
-      (when (> secs 240)
-        (setq secs 240))
-      (unless (< secs 30) ;; Skip titles shorter than 30 seconds
-        (setq secs (- (/ secs 2) emms-playing-time))
-        (unless (< secs 0)
-          (setq emms-lastfm-timer
-                (run-with-timer secs nil 'emms-lastfm-submit-track))))))
-  ;; Update the now playing info displayed on the user's last.fm page.  This
-  ;; doesn't affect the user's profile, so it can be done even for tracks that
-  ;; should not be submitted.
-  (emms-lastfm-submit-now-playing))
-
-(defun emms-lastfm-http-POST (url string sentinel &optional sentinel-args)
-  "Perform a HTTP POST request to URL using STRING as data.
-STRING will be encoded to utf8 before the request.  Call SENTINEL
-with the result buffer."
-  (let ((url-http-attempt-keepalives nil)
-        (url-show-status emms-lastfm-submission-verbose-p)
-        (url-request-method "POST")
-        (url-request-extra-headers
-         '(("Content-type"
-            . "application/x-www-form-urlencoded; charset=utf-8")))
-        (url-request-data (encode-coding-string string 'utf-8)))
-  (url-retrieve url sentinel sentinel-args)))
-
-(defun emms-lastfm-http-GET (url sentinel &optional sentinel-args)
-  "Perform a HTTP GET request to URL.
-Call SENTINEL with SENTINEL-ARGS and the result buffer."
-  (let ((url-show-status emms-lastfm-submission-verbose-p)
-        (url-request-method "GET"))
-    (url-retrieve url sentinel sentinel-args)))
-
-(defun emms-lastfm-submit-now-playing ()
-  "Submit now-playing infos to last.fm.
-These will be displayed on the user's last.fm page."
-  (let* ((artist (emms-track-get emms-lastfm-current-track 'info-artist))
-         (title  (emms-track-get emms-lastfm-current-track 'info-title))
-         (album  (emms-track-get emms-lastfm-current-track 'info-album))
-         (track-number (emms-track-get emms-lastfm-current-track
-                                       'info-tracknumber))
-         (musicbrainz-id "")
-         (track-length (number-to-string
-                        (or (emms-track-get emms-lastfm-current-track
-                                            'info-playing-time)
-                            0))))
-    ;; wait up to 5 seconds to submit np infos in order to finish handshaking.
-    (dotimes (i 5)
-      (when (not (and emms-lastfm-session-id
-                      emms-lastfm-now-playing-url))
-        (sit-for 1)))
-    (when (and emms-lastfm-session-id
-               emms-lastfm-now-playing-url)
-      (emms-lastfm-http-POST emms-lastfm-now-playing-url
-                             (concat "&s="    emms-lastfm-session-id
-                                     "&a[0]=" (emms-url-quote artist)
-                                     "&t[0]=" (emms-url-quote title)
-                                     "&b[0]=" (emms-url-quote album)
-                                     "&l[0]=" track-length
-                                     "&n[0]=" track-number
-                                     "&m[0]=" musicbrainz-id)
-                             'emms-lastfm-submit-now-playing-sentinel))))
-
-(defun emms-lastfm-submit-now-playing-sentinel (&rest args)
-  "Parses the server reponse and inform the user if all worked
-well or if an error occured."
-  (let ((buffer (current-buffer)))
-    (emms-http-decode-buffer buffer)
-    (goto-char (point-min))
-    ;; skip to the first empty line and go one line further.  There the last.fm
-    ;; response starts.
-    (re-search-forward "^$" nil t)
-    (forward-line)
-    (if (re-search-forward "^OK$" nil t)
-        (progn
-          (when emms-lastfm-submission-verbose-p
-            (message "EMMS: Now playing infos submitted to last.fm"))
-          (kill-buffer buffer))
-      (message "EMMS: Now playing infos couldn't be submitted to last.fm: %s"
-               (emms-read-line)))))
-
-(defun emms-lastfm-cancel-timer ()
-  "Cancels `emms-lastfm-timer' if it is running."
-  (emms-cancel-timer emms-lastfm-timer)
-  (setq emms-lastfm-timer nil))
-
-(defun emms-lastfm-pause ()
-  "Handles things to be done when the player is paused or
-resumed."
-  (if emms-player-paused-p
-      ;; the player paused
-      (emms-lastfm-cancel-timer)
-    ;; The player resumed
-    (emms-lastfm-new-track-function)))
-
-(defun emms-lastfm (&optional ARG)
-  "Start submitting the tracks you listened to to
-http://www.last.fm, if ARG is positive. If ARG is negative or
-zero submission of the tracks will be stopped. This applies to
-the current track, too."
-  (interactive "p")
-  (cond
-   ((not (and emms-lastfm-username emms-lastfm-password))
-    (message "%s"
-             (concat "EMMS: In order to activate the last.fm plugin you "
-                     "first have to set both `emms-lastfm-username' and "
-                     "`emms-lastfm-password'")))
-   ((not emms-playing-time-p)
-    (message "%s"
-             (concat "EMMS: The last.fm plugin needs the functionality "
-                     "provided by `emms-playing-time'. It seems that you "
-                     "disabled it explicitly in your init file using code "
-                     "like this: `(emms-playing-time -1)'. Delete that "
-                     "line and have a look at `emms-playing-time's doc "
-                     "string")))
-   (t
-    (if (and ARG (> ARG 0))
-        (progn
-          ;; Append it. Else the playing time could be started a bit too late.
-          (add-hook 'emms-player-started-hook
-                    'emms-lastfm-handshake-if-needed t)
-          ;; Has to be appended, because it has to run after
-          ;; `emms-playing-time-start'
-          (add-hook 'emms-player-started-hook
-                    'emms-lastfm-new-track-function t)
-          (add-hook 'emms-player-stopped-hook
-                    'emms-lastfm-cancel-timer)
-          (add-hook 'emms-player-paused-hook
-                    'emms-lastfm-pause)
-          ;; Clean up after EMMS radio
-          (remove-hook 'emms-player-started-hook
-                       'emms-lastfm-cancel-timer-after-stop)
-          (message "EMMS Last.fm plugin activated"))
-      (remove-hook 'emms-player-started-hook
-                   'emms-lastfm-handshake-if-needed)
-      (remove-hook 'emms-player-started-hook
-                   'emms-lastfm-new-track-function)
-      (remove-hook 'emms-player-stopped-hook
-                   'emms-lastfm-cancel-timer)
-      (remove-hook 'emms-player-paused-hook
-                   'emms-lastfm-pause)
-      (when emms-lastfm-timer (emms-cancel-timer emms-lastfm-timer))
-      (setq emms-lastfm-session-id nil
-            emms-lastfm-submit-url    nil
-            emms-lastfm-process       nil
-            emms-lastfm-current-track nil)
-      (message "EMMS Last.fm plugin deactivated")))))
-
-(defalias 'emms-lastfm-activate 'emms-lastfm)
-(emms-make-obsolete 'emms-lastfm-activate 'emms-lastfm "EMMS 2.2")
-
-(defun emms-lastfm-enable ()
-  "Enable the emms last.fm plugin."
-  (interactive)
-  (emms-lastfm 1))
-
-(defun emms-lastfm-disable ()
-  "Disable the emms last.fm plugin."
-  (interactive)
-  (emms-lastfm -1))
-
-(defun emms-lastfm-restart ()
-  "Disable and reenable the last.fm plugin. This will cause a new
-handshake."
-  (emms-lastfm-disable)
-  (emms-lastfm-enable))
-
-(defun emms-lastfm-handshake-if-needed ()
-  (when (not (and emms-lastfm-session-id
-                  emms-lastfm-submit-url
-                  emms-lastfm-now-playing-url))
-    (emms-lastfm-handshake)))
-
-(defun emms-lastfm-current-unix-time-string ()
-  (replace-regexp-in-string "\\..*" "" (number-to-string (float-time))))
-
-(defun emms-lastfm-handshake ()
-  "Handshakes with the last.fm server."
-  (let ((timestamp (emms-lastfm-current-unix-time-string)))
-    (emms-lastfm-http-GET
-     (concat emms-lastfm-server
-             "?hs=true"
-             "&p=" (number-to-string emms-lastfm-protocol-version)
-             "&c=" emms-lastfm-client-id
-             "&v=" (number-to-string emms-lastfm-client-version)
-             "&u=" (emms-url-quote emms-lastfm-username)
-             "&t=" timestamp
-             "&a=" (md5 (concat (md5 emms-lastfm-password) timestamp)))
-     'emms-lastfm-handshake-sentinel)))
-
-(defun emms-lastfm-handshake-sentinel (&rest args)
-  "Parses the server reponse and inform the user if all worked
-well or if an error occured."
-  (let ((buffer (current-buffer)))
-    (emms-http-decode-buffer buffer)
-    (goto-char (point-min))
-    ;; skip to the first empty line and go one line further.  There the last.fm
-    ;; response starts.
-    (re-search-forward "^$" nil t)
-    (forward-line)
-    (let ((response (emms-read-line)))
-      (if (not (string-match (rx (or "OK")) response))
-          (message "EMMS: Handshake failed: %s" response)
-        (forward-line)
-        (setq emms-lastfm-session-id (emms-read-line))
-        (forward-line)
-        (setq emms-lastfm-now-playing-url (emms-read-line))
-        (forward-line)
-        (setq emms-lastfm-submit-url (emms-read-line))
-        (message "EMMS: Handshaking with server done")
-        (kill-buffer buffer)))))
-
-(defun emms-lastfm-submit-track ()
-  "Submits the current track (`emms-lastfm-current-track') to
-last.fm."
-  (let* ((artist (emms-track-get emms-lastfm-current-track 'info-artist))
-         (title  (emms-track-get emms-lastfm-current-track 'info-title))
-         (album  (emms-track-get emms-lastfm-current-track 'info-album))
-         (track-number (emms-track-get emms-lastfm-current-track 
'info-tracknumber))
-         (musicbrainz-id "")
-         (track-length (number-to-string
-                        (emms-track-get emms-lastfm-current-track
-                                        'info-playing-time))))
-    (emms-lastfm-http-POST
-     emms-lastfm-submit-url
-     (concat "&s="    emms-lastfm-session-id
-             "&a[0]=" (emms-url-quote artist)
-             "&t[0]=" (emms-url-quote title)
-             "&i[0]=" emms-lastfm-current-track-starting-time-string
-             "&o[0]=P" ;; TODO: Maybe support others.  See the API.
-             "&r[0]="  ;; The rating.  Empty if not applicable (for P it's not)
-             "&l[0]=" track-length
-             "&b[0]=" (emms-url-quote album)
-             "&n[0]=" track-number
-             "&m[0]=" musicbrainz-id)
-     'emms-lastfm-submission-sentinel)))
-
-(defun emms-lastfm-submission-sentinel (&rest args)
-  "Parses the server reponse and inform the user if all worked
-well or if an error occured."
-  (let ((buffer (current-buffer)))
-    (emms-http-decode-buffer buffer)
-    (goto-char (point-min))
-    ;; skip to the first empty line and go one line further.  There the last.fm
-    ;; response starts.
-    (re-search-forward "^$" nil t)
-    (forward-line)
-    (if (re-search-forward "^OK$" nil t)
-        (progn
-          (when emms-lastfm-submission-verbose-p
-            (message "EMMS: \"%s\" submitted to last.fm"
-                     (emms-track-description emms-lastfm-current-track)))
-          (kill-buffer buffer))
-      (message "EMMS: Song couldn't be submitted to last.fm: %s"
-               (emms-read-line)))))
-
-;;; Playback of lastfm:// streams
-
-(defgroup emms-player-lastfm-radio nil
-  "EMMS player for Last.fm streams."
-  :group 'emms-player
-  :prefix "emms-player-lastfm-")
-
-(defcustom emms-player-lastfm-radio (emms-player 'emms-lastfm-radio-start
-                                                 'ignore ; no need to stop
-                                                 'emms-lastfm-radio-playable-p)
-  "*Parameters for the Last.fm radio player."
-  :type '(cons symbol alist)
-  :group 'emms-player-lastfm-radio)
-
-(defconst emms-lastfm-radio-base-url "http://ws.audioscrobbler.com/radio/";
-  "The base URL for playing lastfm:// stream.
--- only used internally --")
-
-(defvar emms-lastfm-radio-session nil "-- only used internally --")
-(defvar emms-lastfm-radio-stream-url nil "-- only used internally --")
-
-(defun emms-lastfm-radio-get-handshake-url ()
-  (concat emms-lastfm-radio-base-url
-          "handshake.php?version=" (number-to-string
-                                    emms-lastfm-client-version)
-          "&platform="              emms-lastfm-client-id
-          "&username="              (emms-url-quote emms-lastfm-username)
-          "&passwordmd5="           (md5 emms-lastfm-password)
-          "&debug="                 (number-to-string 9)))
-
-(defun emms-lastfm-radio-handshake (fn radio-url)
-  "Handshakes with the last.fm server.
-Calls FN when done with RADIO-URL as its only argument."
-  (emms-lastfm-http-GET (emms-lastfm-radio-get-handshake-url)
-                        'emms-lastfm-radio-handshake-sentinel
-                        (list fn radio-url)))
-
-(defun emms-lastfm-radio-handshake-sentinel (status fn radio-url)
-  (let ((buffer (current-buffer)))
-    (emms-http-decode-buffer buffer)
-    (setq emms-lastfm-radio-session    (emms-key-value "session"))
-    (setq emms-lastfm-radio-stream-url (emms-key-value "stream_url"))
-    (kill-buffer buffer)
-    (if (and emms-lastfm-radio-session emms-lastfm-radio-stream-url)
-        (progn
-          (message "EMMS: Handshaking for Last.fm playback successful")
-          (funcall fn radio-url))
-      (message "EMMS: Failed handshaking for Last.fm playback"))))
-
-(defun emms-lastfm-radio-1 (lastfm-url)
-  "Internal function used by `emms-lastfm-radio'."
-  (if (and emms-lastfm-radio-session
-           emms-lastfm-radio-stream-url)
-      (progn
-        (emms-lastfm-http-GET
-         (concat emms-lastfm-radio-base-url
-                 "adjust.php?"
-                 "session=" emms-lastfm-radio-session
-                 "&url="    (emms-url-quote lastfm-url)
-                 "&debug="  (number-to-string 0))
-         'emms-lastfm-radio-sentinel))
-    (message "EMMS: Cannot play Last.fm stream")))
-
-(defun emms-lastfm-radio (lastfm-url)
-  "Plays the stream associated with the given Last.fm URL. (A
-Last.fm URL has the form lastfm://foo/bar/baz, e.g.
-
-  lastfm://artist/Manowar/similarartists
-
-or
-
-  lastfm://globaltags/metal."
-  (interactive "sLast.fm URL: ")
-  ;; Streamed songs must not be added to the lastfm profile
-  (emms-lastfm-disable)
-  (if (not (and emms-lastfm-radio-session
-                emms-lastfm-radio-stream-url))
-      (emms-lastfm-radio-handshake #'emms-lastfm-radio-1 lastfm-url)
-    (emms-lastfm-radio-1 lastfm-url)))
-
-(defun emms-lastfm-radio-playable-p (track)
-  "Determine whether the Last.fm player can play this track."
-  (let ((name (emms-track-get track 'name))
-        (type (emms-track-get track 'type)))
-    (and (eq type 'lastfm)
-         (string-match "^lastfm://" name))))
-
-(defun emms-lastfm-radio-start (track)
-  "Start playing TRACK."
-  (when (emms-lastfm-radio-playable-p track)
-    (let ((name (emms-track-get track 'name)))
-      (emms-lastfm-radio name))))
-
-(defcustom emms-lastfm-radio-metadata-period 15
-  "When listening to Last.fm Radio every how many seconds should
-emms-lastfm poll for metadata? If set to nil, there won't be any
-polling at all.
-
-The default is 15: That means that the mode line will display the
-wrong (last) track's data for a maximum of 15 seconds. If your
-network connection has a big latency this value may be too
-high. (But then streaming a 128KHz mp3 won't be fun anyway.)"
-  :type '(choice integer
-                 (const :tag "Disable" nil))
-  :group 'emms-lastfm)
-
-(defun emms-lastfm-cancel-timer-after-stop ()
-  (add-hook 'emms-player-stopped-hook
-            'emms-lastfm-cancel-timer))
-
-(defun emms-lastfm-radio-sentinel (&rest args)
-  (let ((buffer (current-buffer)))
-    (emms-http-decode-buffer buffer)
-    (if (string= (emms-key-value "response" buffer) "OK")
-        (progn
-          (kill-buffer buffer)
-          (add-hook 'emms-player-started-hook
-                    'emms-lastfm-cancel-timer-after-stop)
-          (emms-play-url emms-lastfm-radio-stream-url)
-          (when emms-lastfm-radio-metadata-period
-            (when emms-lastfm-timer
-              (emms-lastfm-cancel-timer))
-            (setq emms-lastfm-timer
-                  (run-with-timer 0 emms-lastfm-radio-metadata-period
-                                  'emms-lastfm-radio-request-metadata)))
-          (message "EMMS: Playing Last.fm stream"))
-      (kill-buffer buffer)
-      (message "EMMS: Bad response from Last.fm"))))
-
-(defun emms-lastfm-np (&optional insertp callback)
-  "Show the currently-playing lastfm radio tune.
-
-If INSERTP is non-nil, insert the description into the current
-buffer instead.
-
-If CALLBACK is a function, call it with the current buffer and
-description as arguments instead of displaying the description or
-inserting it."
-  (interactive "P")
-  (emms-lastfm-radio-request-metadata
-   (lambda (status insertp buffer callback)
-     (let ((response-buf (current-buffer))
-           artist title)
-       (emms-http-decode-buffer response-buf)
-       (setq artist (emms-key-value "artist" response-buf)
-             title  (emms-key-value "track" response-buf))
-       (kill-buffer response-buf)
-       (let ((msg (if (and title artist)
-                      (format emms-show-format
-                              (format "%s - %s" artist title))
-                    "Nothing playing right now")))
-         (cond ((functionp callback)
-                (when (and title artist)
-                  (funcall callback buffer msg)))
-               ((and insertp title artist)
-                (with-current-buffer buffer
-                  (insert msg)))
-               (t (message msg))))))
-   (list insertp (current-buffer) callback)))
-
-(defun emms-lastfm-read-artist ()
-  "Read an artist name from the user."
-  (let ((artists nil))
-    (when (boundp 'emms-cache-db)
-      (maphash
-       #'(lambda (file track)
-           (let ((artist (emms-track-get track 'info-artist)))
-             (when artist
-               (add-to-list 'artists artist))))
-       emms-cache-db))
-    (if artists
-        (emms-completing-read "Artist: " artists)
-      (read-string "Artist: "))))
-
-(defun emms-play-lastfm-similar-artists (artist)
-  "Plays the similar artist radio of ARTIST."
-  (interactive (list (emms-lastfm-read-artist)))
-  (emms-lastfm-radio (concat "lastfm://artist/"
-                             artist
-                             "/similarartists")))
-
-(defun emms-play-lastfm-global-tag (tag)
-  "Plays the global tag radio of TAG."
-  (interactive "sGlobal Tag: ")
-  (emms-lastfm-radio (concat "lastfm://globaltags/" tag)))
-
-(defun emms-play-lastfm-artist-fan (artist)
-  "Plays the artist fan radio of ARTIST."
-  (interactive (list (emms-lastfm-read-artist)))
-  (emms-lastfm-radio (concat "lastfm://artist/" artist "/fans")))
-
-(defun emms-lastfm-radio-love ()
-  "Inform Last.fm that you love the currently playing song."
-  (interactive)
-  (emms-lastfm-radio-rating "love"))
-
-(defun emms-lastfm-radio-skip ()
-  "Inform Last.fm that you want to skip the currently playing
-song."
-  (interactive)
-  (emms-lastfm-radio-rating "skip"))
-
-(defun emms-lastfm-radio-ban ()
-  "Inform Last.fm that you want to ban the currently playing
-song."
-  (interactive)
-  (emms-lastfm-radio-rating "ban"))
-
-(defun emms-lastfm-radio-rating (command)
-  (emms-lastfm-http-GET
-   (concat emms-lastfm-radio-base-url
-           "control.php?"
-           "session="  emms-lastfm-radio-session
-           "&command=" command
-           "&debug="   (number-to-string 0))
-   'emms-lastfm-radio-rating-sentinel))
-
-(defun emms-lastfm-radio-rating-sentinel (&rest args)
-  (let ((buffer (current-buffer)))
-    (emms-http-decode-buffer buffer)
-    (if (string= (emms-key-value "response" buffer) "OK")
-        (message "EMMS: Rated current track")
-      (message "EMMS: Rating failed"))
-    (kill-buffer buffer)))
-
-(defun emms-lastfm-radio-request-metadata (&optional fn data)
-  "Request the metadata of the current song and display it.
-
-If FN is given, call it instead of
-`emms-lastfm-radio-request-metadata-sentinel', with DATA as its
-first parameter.
-
-If DATA is given, it should be a list."
-  (interactive)
-  (emms-lastfm-http-GET
-   (concat emms-lastfm-radio-base-url
-           "np.php?"
-           "session=" emms-lastfm-radio-session
-           "&debug="  (number-to-string 0))
-   (or fn 'emms-lastfm-radio-request-metadata-sentinel)
-   data))
-
-(defun emms-lastfm-radio-request-metadata-sentinel (&rest args)
-  (let ((buffer (current-buffer)))
-    (emms-http-decode-buffer buffer)
-    (let ((artist (emms-key-value "artist" buffer))
-          (title  (emms-key-value "track" buffer))
-          (track (emms-playlist-current-selected-track)))
-      (kill-buffer buffer)
-      (emms-track-set track 'info-artist artist)
-      (emms-track-set track 'info-title title)
-      (emms-track-updated track))))
-
-
-;;; Utility functions
-
-(defun emms-read-line ()
-  (buffer-substring-no-properties (line-beginning-position)
-                                  (line-end-position)))
-
-(defun emms-key-value (key &optional buffer)
-  "Returns the value of KEY from BUFFER.
-If BUFFER is nil, use the current buffer.
-
-BUFFER has to contain a key-value list like:
-
-foo=bar
-x=17"
-  (unless (and buffer (not (buffer-live-p buffer)))
-    (with-current-buffer (or buffer (current-buffer))
-      (goto-char (point-min))
-      (when (re-search-forward (concat "^" key "=") nil t)
-        (buffer-substring-no-properties (point) (line-end-position))))))
-
-(provide 'emms-lastfm)
-;;; emms-lastfm.el ends here
-- 
debian.1.5.6.1.19.ge6b2





reply via email to

[Prev in Thread] Current Thread [Next in Thread]