diff options
Diffstat (limited to 'lisp/emms-lastfm.el')
-rw-r--r-- | lisp/emms-lastfm.el | 673 |
1 files changed, 673 insertions, 0 deletions
diff --git a/lisp/emms-lastfm.el b/lisp/emms-lastfm.el new file mode 100644 index 0000000..1f597d7 --- /dev/null +++ b/lisp/emms-lastfm.el @@ -0,0 +1,673 @@ +;;; emms-lastfm.el --- add your listened songs to your profile at last.fm + +;; Copyright (C) 2006, 2007 Free Software Foundation, Inc. + +;; Author: Tassilo Horn <tassilo@member.fsf.org> + +;; 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 and +;; the Global Tag Radio. Here you only need to enter the band's name or the tag +;; respectively. +;; `M-x emms-lastfm-radio-similar-artists RET Britney Spears' +;; `M-x emms-lastfm-radio-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) + (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-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) + (emms-play-url emms-lastfm-radio-stream-url) + (when emms-lastfm-radio-metadata-period + (setq emms-lastfm-timer + (run-with-timer 0 emms-lastfm-radio-metadata-period + 'emms-lastfm-radio-request-metadata)) + (add-hook 'emms-player-stopped-hook + 'emms-lastfm-cancel-timer)) + (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-radio-similar-artists (artist) + "Plays the similar artist radio of ARTIST." + (interactive "sArtist: ") + (emms-lastfm-radio (concat "lastfm://artist/" + artist + "/similarartists"))) + +(defun emms-lastfm-radio-global-tag (tag) + "Plays the global tag radio of TAG." + (interactive "sGlobal Tag: ") + (emms-lastfm-radio (concat "lastfm://globaltags/" tag))) + +(defun emms-lastfm-radio-artist-fan (artist) + "Plays the artist fan radio of ARTIST." + (interactive "sArtist: ") + (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 |