2 Copyright (C) 2006 by Jonas Kramer
3 Published under the terms of the GNU General Public License (GPL).
14 #include <sys/types.h>
32 struct hash data
; /* Warning! MUST be bzero'd ASAP or we're all gonna die! */
34 pid_t playfork
= 0; /* PID of the decoding & playing process, if running */
37 struct playlist playlist
;
38 char * current_station
= NULL
;
40 int authenticate(const char * username
, const char * password
) {
41 const unsigned char * md5
;
42 char hexmd5
[32 + 1] = { 0 }, url
[512] = { 0 }, ** response
;
43 char * encuser
= NULL
;
44 unsigned ndigit
, i
= 0;
45 const char * session
, * fmt
=
46 "http://ws.audioscrobbler.com/radio/handshake.php"
54 memset(& data
, 0, sizeof(struct hash
));
56 /* create the hash, then convert to ASCII */
57 md5
= MD5((const unsigned char *) password
, strlen(password
));
58 for(ndigit
= 0; ndigit
< 16; ++ndigit
)
59 sprintf(2 * ndigit
+ hexmd5
, "%02x", md5
[ndigit
]);
61 set(& rc
, "password", hexmd5
);
63 /* escape username for URL */
64 encode(username
, & encuser
);
66 /* put handshake URL together and fetch initial data from server */
67 snprintf(url
, sizeof(url
), fmt
, encuser
, hexmd5
);
70 response
= fetch(url
, NULL
, NULL
, NULL
);
72 fputs("No response.\n", stderr
);
77 char key
[64] = { 0 }, val
[256] = { 0 };
78 sscanf(response
[i
], "%63[^=]=%255[^\r\n]", key
, val
);
79 set(& data
, key
, val
);
84 session
= value(& data
, "session");
85 if(!session
|| !strcmp(session
, "FAILED")) {
86 fputs("Authentication failed.\n", stderr
);
87 unset(& data
, "session");
95 int station(const char * stationURL
) {
96 char url
[512] = { 0 }, * encodedURL
= NULL
, ** response
, name
[512], * completeURL
;
97 unsigned i
= 0, retval
= !0, regular
= !0;
99 const char * types
[4] = {"play", "preview", "track", "playlist"};
103 if(playfork
&& haskey(& rc
, "delay-change")) {
106 Cancel station change if url is empty or equal to the current
112 if(!strlen(stationURL
) || !strcmp(stationURL
, current_station
)) {
113 puts("Station change cancelled.");
118 /* Do nothing if the station is already played. */
119 else if(current_station
&& !strcmp(current_station
, stationURL
)) {
123 /* Do nothing if the station URL is empty. */
124 else if(!strlen(stationURL
)) {
129 nextstation
= strdup(stationURL
);
134 /* Do nothing if the station is already played. */
135 else if(current_station
&& !strcmp(current_station
, stationURL
)) {
139 freelist(& playlist
);
141 if(!haskey(& data
, "session")) {
142 fputs("Not authenticated, yet.\n", stderr
);
146 if(!stationURL
|| !strlen(stationURL
))
149 if(!strncasecmp(stationURL
, "lastfm://", 9)) {
150 completeURL
= strdup(stationURL
);
154 int size
= strlen(stationURL
) + 10;
155 completeURL
= malloc(size
);
156 snprintf(completeURL
, size
, "lastfm://%s", stationURL
);
159 /* Check if it's a static playlist of tracks or track previews. */
160 for(i
= 0; i
< 4; ++i
)
161 if(!strncasecmp(types
[i
], stationURL
, strlen(types
[i
]))) {
167 If this is not a special "one-time" stream, it's a regular radio
168 station and we request it using the good old /adjust.php URL.
169 If it's not a regular stream, the reply of this request already is
170 a XSPF playlist we have to parse.
173 fmt
= "http://ws.audioscrobbler.com/radio/adjust.php?session=%s&url=%s";
177 "http://ws.audioscrobbler.com/1.0/webclient/getresourceplaylist.php"
178 "?sk=%s&url=%s&desktop=1";
181 encode(completeURL
, & encodedURL
);
182 snprintf(url
, sizeof(url
), fmt
, value(& data
, "session"), encodedURL
);
187 if(!(response
= fetch(url
, NULL
, NULL
, NULL
)))
191 for(i
= 0; response
[i
]; ++i
) {
192 char status
[64] = { 0 };
194 if(sscanf(response
[i
], "response=%63[^\r\n]", status
) > 0)
195 if(!strncmp(status
, "FAILED", 6))
198 if(sscanf(response
[i
], "stationname=%127[^\r\n]", name
) > 0) {
199 if(playlist
.title
!= NULL
)
200 free(playlist
.title
);
202 decode(name
, & playlist
.title
);
203 unhtml(playlist
.title
);
211 printf("Sorry, couldn't set station to %s.\n", stationURL
);
218 char * xml
= join(response
, 0);
222 freelist(& playlist
);
224 if(!parsexspf(& playlist
, xml
))
234 free(current_station
);
236 current_station
= strdup(stationURL
);
237 assert(current_station
!= NULL
);
239 if(retval
&& playfork
) {
241 kill(playfork
, SIGUSR1
);
249 Takes pointer to a playlist, forks off a playback process and tries to play
250 the next track in the playlist. If there's already a playback process, it's
251 killed first (which means the currently played track is skipped).
253 int play(struct playlist
* list
) {
257 "creator", "title", "album", "duration", "station",
258 "lastfm:trackauth", "trackpage", "artistpage", "albumpage",
259 "image", "freeTrackURL",
262 assert(list
!= NULL
);
268 kill(playfork
, SIGUSR1
);
276 for(i
= 0; i
< (sizeof(keys
) / sizeof(char *)); ++i
)
277 set(& track
, keys
[i
], value(& list
->track
->track
, keys
[i
]));
279 if(pipe(pipefd
) != 0)
286 const char * location
= value(& list
->track
->track
, "location");
293 if(location
!= NULL
) {
294 fetch(location
, & fd
, NULL
, NULL
);
298 If there was an error, tell the main process about it by
301 if(!playback(fd
, pipefd
[0]))
302 kill(getppid(), SIGUSR2
);
317 playpipe
= pipefd
[1];