fordon_greeman / voidfiles

Branch: main
Switch branches/tags
main
theidioticdev add emacs 2079ec5 23h ago
384 lines · 12.2 KB Raw Blame History
1 ;;; emms-librefm-stream.el --- Libre.FM streaming -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2014 Free Software Foundation, Inc.
4
5 ;; Author: Yoni Rabkin <yrk@gnu.org>
6
7 ;; Keywords: emms, libre.fm, GNU FM
8
9 ;; EMMS is free software; you can redistribute it and/or modify it
10 ;; under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 3, or (at your option)
12 ;; any later version.
13 ;;
14 ;; EMMS is distributed in the hope that it will be useful, but WITHOUT
15 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
17 ;; License for more details.
18 ;;
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with EMMS; see the file COPYING. If not, write to the Free
21 ;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 ;; MA 02110-1301, USA.
23
24
25 ;;; Code:
26
27 (require 'xml)
28 (require 'emms-playlist-mode)
29 (require 'emms-librefm-scrobbler)
30
31
32 (defvar emms-librefm-stream-host-url
33 "alpha.libre.fm"
34 "URL for the streaming host")
35
36 (defvar emms-librefm-stream-host-base-path
37 ""
38 "URL for the streaming host base path")
39
40 (defvar emms-librefm-stream-session-id
41 ""
42 "Session ID for radio.")
43
44 (defvar emms-librefm-stream-debug
45 ""
46 "Temporary debug information.")
47
48 (defvar emms-librefm-stream-station-name
49 ""
50 "Last station name tuned to.")
51
52 (defvar emms-librefm-stream-emms-tracklist
53 ""
54 "List of tracks for streaming.")
55
56 (defvar emms-librefm-stream-playlist-buffer-name
57 "*Emms GNU FM*"
58 "Name for non-interactive Emms GNU FM buffer.")
59
60 (defvar emms-librefm-stream-playlist-buffer nil
61 "Non-interactive Emms GNU FM buffer.")
62
63 (defvar emms-librefm-stream-connect-method "https://"
64 "Method of connecting to server.")
65
66
67 ;;; ------------------------------------------------------------------
68 ;;; HTTP
69 ;;; ------------------------------------------------------------------
70
71 (defun emms-librefm-stream-assert-http ()
72 "Assert a sane HTTP response from the server.
73
74 This function must be called inside the response buffer. Leaves
75 point after the HTTP headers."
76 (goto-char (point-min))
77 (when (not (re-search-forward "^.*200 OK$" (line-end-position) t))
78 (error "bad HTTP server response"))
79 ;; go to the start of the FM response
80 (when (not (re-search-forward "\n\n" (point-max) t))
81 (error "bad FM server response")))
82
83
84 ;;; ------------------------------------------------------------------
85 ;;; radio handshake
86 ;;; ------------------------------------------------------------------
87
88 (defun emms-librefm-stream-tune-handshake-string ()
89 "Create the tune handshake string."
90 (let ((username (emms-librefm-scrobbler--username))
91 (password (emms-librefm-scrobbler--password)))
92 (let ((url (concat emms-librefm-stream-connect-method
93 emms-librefm-stream-host-url
94 "/radio/handshake.php?"
95 "version=1.3.0.58" "&"
96 "platform=linux" "&"
97 "username=" (url-encode-url username) "&"
98 "passwordmd5=" (md5 password) "&"
99 "language=en")))
100 url)))
101
102 (defun emms-librefm-stream-tune-handshake-call ()
103 "Make the tune handshake call."
104 (let ((url-request-method "POST"))
105 (ignore url-request-method)
106 (let ((response
107 (url-retrieve-synchronously
108 (emms-librefm-stream-tune-handshake-string))))
109 (setq emms-librefm-stream-debug
110 (with-current-buffer response
111 (buffer-substring-no-properties (point-min)
112 (point-max))))
113 response)))
114
115 (defun emms-librefm-stream-handle-tune-handshake-response (resbuf)
116 "Handle the tune handshake server response."
117 (when (not (bufferp resbuf))
118 (error "response not a buffer"))
119 (with-current-buffer resbuf
120 (emms-librefm-stream-assert-http)
121 (let (radio-session-id
122 base-url
123 base-path
124 (start (point)))
125
126 (if (re-search-forward "^session=\\(.*\\)$" (point-max) t)
127 (setq radio-session-id (match-string-no-properties 1))
128 (error "no radio session ID from server"))
129
130 (goto-char start)
131 (if (re-search-forward "^base_url=\\(.*\\)$" (point-max) t)
132 (setq base-url (match-string-no-properties 1))
133 (error "no base url from server"))
134
135 (goto-char start)
136 (if (re-search-forward "^base_path=\\(.*\\)$" (point-max) t)
137 (setq base-path (match-string-no-properties 1))
138 (error "no base path from server"))
139
140 (setq emms-librefm-stream-session-id radio-session-id
141 emms-librefm-stream-host-url base-url
142 emms-librefm-stream-host-base-path base-path))
143
144 (message "radio handshake successful")))
145
146 (defun emms-librefm-stream-tune-handshake ()
147 "Make and handle the tune handshake."
148 (emms-librefm-stream-handle-tune-handshake-response
149 (emms-librefm-stream-tune-handshake-call)))
150
151
152 ;;; ------------------------------------------------------------------
153 ;;; tuning
154 ;;; ------------------------------------------------------------------
155
156 (defun emms-librefm-stream-tune-string (session-id station)
157 "Create the tune string."
158 (when (not session-id)
159 (error "null session id"))
160 (when (not station)
161 (error "null station"))
162 (let ((url (concat emms-librefm-stream-connect-method
163 emms-librefm-stream-host-url
164 emms-librefm-stream-host-base-path
165 "/adjust.php?"
166 "session=" session-id "&"
167 "url=" (url-encode-url station))))
168 url))
169
170 (defun emms-librefm-stream-tune-call (session-id station)
171 "Make the tune call."
172 (let ((url-request-method "POST"))
173 (ignore url-request-method)
174 (let ((response
175 (url-retrieve-synchronously
176 (emms-librefm-stream-tune-string
177 session-id station))))
178 (setq emms-librefm-stream-debug
179 (with-current-buffer response
180 (buffer-substring-no-properties (point-min)
181 (point-max))))
182 response)))
183
184 (defun emms-librefm-stream-handle-tune-response (resbuf)
185 "Handle the tune server response."
186 (when (not (bufferp resbuf))
187 (error "response not a buffer"))
188 (with-current-buffer resbuf
189 (emms-librefm-stream-assert-http)
190 (let (response
191 stationname
192 (start (point)))
193
194 (if (re-search-forward "^response=\\(.*\\)$" (point-max) t)
195 (setq response (match-string-no-properties 1))
196 (error "no response status code"))
197 (when (not (string= response "OK"))
198 (error "tune response not OK"))
199
200 (goto-char start)
201 (if (re-search-forward "^stationname=\\(.*\\)$" (point-max) t)
202 (setq stationname (match-string-no-properties 1))
203 (error "no stationname from server"))
204
205 (setq emms-librefm-stream-station-name stationname)
206
207 (message "successfully tuned to: %s" stationname))))
208
209 (defun emms-librefm-stream-tune (station)
210 "Make and handle tune call."
211 (emms-librefm-stream-handle-tune-response
212 (emms-librefm-stream-tune-call
213 emms-librefm-stream-session-id
214 station)))
215
216
217 ;;; ------------------------------------------------------------------
218 ;;; radio.getPlaylist
219 ;;; ------------------------------------------------------------------
220
221 (defun emms-librefm-stream-getplaylist-string (radio-session-id)
222 "Create the getplaylist string."
223 (when (not radio-session-id)
224 (error "null radio session id"))
225 (let ((url (concat emms-librefm-stream-connect-method
226 emms-librefm-stream-host-url
227 emms-librefm-stream-host-base-path
228 "/xspf.php?"
229 "sk=" radio-session-id "&"
230 "discovery=0" "&"
231 "desktop=1.3.0.58")))
232 url))
233
234 (defun emms-librefm-stream-getplaylist-call (session-id)
235 "Make the getplaylist call."
236 (let ((url-request-method "POST"))
237 (ignore url-request-method)
238 (let ((response
239 (url-retrieve-synchronously
240 (emms-librefm-stream-getplaylist-string session-id))))
241 (setq emms-librefm-stream-debug
242 (with-current-buffer response
243 (buffer-substring-no-properties (point-min)
244 (point-max))))
245 response)))
246
247 (defun emms-librefm-stream-handle-getplaylist-response (resbuf)
248 "Handle the getplaylist server response."
249 (when (not (bufferp resbuf))
250 (error "response not a buffer"))
251 (with-current-buffer resbuf
252 (emms-librefm-stream-assert-http)
253 (xml-parse-region (point) (point-max))))
254
255 (defun emms-librefm-stream-getplaylist ()
256 "Make and handle radio.getPlaylist."
257 (emms-librefm-stream-handle-getplaylist-response
258 (emms-librefm-stream-getplaylist-call
259 emms-librefm-stream-session-id)))
260
261
262 ;;; ------------------------------------------------------------------
263 ;;; XSPF
264 ;;; ------------------------------------------------------------------
265 (defun emms-librefm-stream-xspf-find (tag data)
266 "Return the tracklist portion of PLAYLIST or nil."
267 (let ((tree (copy-tree data))
268 result)
269 (while (and tree (not result))
270 (let ((this (car tree)))
271 (when (and (listp this)
272 (eq (car this) tag))
273 (setq result this)))
274 (setq tree (cdr tree)))
275 result))
276
277 (defun emms-librefm-stream-xspf-tracklist (playlist)
278 "Return the tracklist portion of PLAYLIST or nil."
279 (emms-librefm-stream-xspf-find 'trackList (car playlist)))
280
281 (defun emms-librefm-stream-xspf-get (tag track)
282 "Return the data associated with TAG in TRACK."
283 (nth 2 (emms-librefm-stream-xspf-find tag track)))
284
285 (defun emms-librefm-stream-xspf-convert-track (track)
286 "Convert TRACK to an Emms track."
287 (let ((location (emms-librefm-stream-xspf-get 'location track))
288 (title (emms-librefm-stream-xspf-get 'title track))
289 (album (emms-librefm-stream-xspf-get 'album track))
290 (creator (emms-librefm-stream-xspf-get 'creator track))
291 (duration (emms-librefm-stream-xspf-get 'duration track)))
292 (let ((emms-track (emms-dictionary '*track*)))
293 (emms-track-set emms-track 'name location)
294 (emms-track-set emms-track 'info-artist creator)
295 (emms-track-set emms-track 'info-title title)
296 (emms-track-set emms-track 'info-album album)
297 (emms-track-set emms-track 'info-playing-time
298 (/ (string-to-number duration)
299 1000))
300 (emms-track-set emms-track 'type 'url)
301 emms-track)))
302
303 (defun emms-librefm-stream-xspf-convert-tracklist (tracklist)
304 "Convert TRACKLIST to a list of Emms tracks."
305 (let (tracks)
306 (mapc
307 #'(lambda (e)
308 (when (and (listp e)
309 (eq 'track (car e)))
310 (setq tracks
311 (append tracks
312 `(,(emms-librefm-stream-xspf-convert-track e))))))
313 tracklist)
314 tracks))
315
316
317 ;;; ------------------------------------------------------------------
318 ;;; stream
319 ;;; ------------------------------------------------------------------
320
321 (defun emms-librefm-stream-set-librefm-playlist-buffer ()
322 "Setup the GNU FM buffer and make it `emms-playlist-buffer'."
323 (when (not (buffer-live-p emms-librefm-stream-playlist-buffer))
324 (setq emms-librefm-stream-playlist-buffer
325 (emms-playlist-new
326 emms-librefm-stream-playlist-buffer-name)))
327 (setq emms-playlist-buffer emms-librefm-stream-playlist-buffer))
328
329 (defun emms-librefm-stream-queue ()
330 "Queue streaming tracks."
331 (let ((tracklist
332 (emms-librefm-stream-xspf-tracklist
333 (emms-librefm-stream-getplaylist))))
334 (when (not tracklist)
335 (setq emms-librefm-stream-emms-tracklist nil)
336 (error "could not find tracklist"))
337 (setq emms-librefm-stream-emms-tracklist
338 (emms-librefm-stream-xspf-convert-tracklist tracklist))
339
340 (emms-librefm-stream-set-librefm-playlist-buffer)
341
342 (with-current-emms-playlist
343 (goto-char (point-max))
344 (save-excursion
345 (mapc
346 #'(lambda (track)
347 (emms-playlist-insert-track track))
348 emms-librefm-stream-emms-tracklist)))))
349
350 (defun emms-librefm-stream-queue-loader ()
351 "Queue more streaming music if needed."
352 (with-current-emms-playlist
353 (goto-char (if emms-playlist-mode-selected-overlay
354 (overlay-start emms-playlist-mode-selected-overlay)
355 (point-min)))
356 (when (and (eq (current-buffer)
357 emms-librefm-stream-playlist-buffer)
358 (not (next-single-property-change (line-end-position)
359 'emms-track)))
360 (emms-librefm-stream-queue))))
361
362 (defun emms-librefm-stream (station)
363 "Stream STATION from a GNU FM server."
364 (interactive "sEnter station URL: ")
365 (when (not (stringp station))
366 (error "bad argument"))
367
368 (add-hook 'emms-player-finished-hook
369 'emms-librefm-stream-queue-loader)
370
371 (emms-librefm-stream-tune-handshake)
372 (emms-librefm-stream-tune station)
373
374 (message "tuned to %s, getting playlist..."
375 emms-librefm-stream-station-name)
376
377 (emms-librefm-stream-queue)
378 (with-current-emms-playlist
379 (emms-playlist-mode-play-current-track)))
380
381
382 (provide 'emms-librefm-stream)
383
384 ;;; emms-librefm-stream.el ends here