Mercurial > audlegacy-plugins
comparison src/scrobbler/plugin.c @ 1026:ff0487e9d00d trunk
[svn] - first attempt at implementing AudioScrobbler 1.2 protocol
| author | nenolod |
|---|---|
| date | Fri, 11 May 2007 22:49:11 -0700 |
| parents | b70103d4b5ba |
| children | ef86db6345e4 |
comparison
equal
deleted
inserted
replaced
| 1025:b70103d4b5ba | 1026:ff0487e9d00d |
|---|---|
| 9 | 9 |
| 10 #include <audacious/plugin.h> | 10 #include <audacious/plugin.h> |
| 11 #include <audacious/ui_preferences.h> | 11 #include <audacious/ui_preferences.h> |
| 12 #include <audacious/playlist.h> | 12 #include <audacious/playlist.h> |
| 13 #include <audacious/configdb.h> | 13 #include <audacious/configdb.h> |
| 14 #include <audacious/auddrct.h> | 14 #include <audacious/beepctrl.h> |
| 15 #include <audacious/strings.h> | 15 #include <audacious/strings.h> |
| 16 #include <audacious/main.h> | 16 #include <audacious/main.h> |
| 17 | 17 |
| 18 #include <unistd.h> | 18 #include <unistd.h> |
| 19 #include <stdio.h> | 19 #include <stdio.h> |
| 28 #include "gtkstuff.h" | 28 #include "gtkstuff.h" |
| 29 #include "config.h" | 29 #include "config.h" |
| 30 #include "fmt.h" | 30 #include "fmt.h" |
| 31 #include "configure.h" | 31 #include "configure.h" |
| 32 | 32 |
| 33 #define XS_CS xmms_scrobbler.xmms_session | |
| 33 #define XS_SLEEP 1 | 34 #define XS_SLEEP 1 |
| 34 #define HS_SLEEP 10 | 35 #define HS_SLEEP 10 |
| 35 | 36 |
| 36 typedef struct submit_t | 37 typedef struct submit_t |
| 37 { | 38 { |
| 42 static void cleanup(void); | 43 static void cleanup(void); |
| 43 static void *xs_thread(void *); | 44 static void *xs_thread(void *); |
| 44 static void *hs_thread(void *); | 45 static void *hs_thread(void *); |
| 45 static int sc_going, ge_going; | 46 static int sc_going, ge_going; |
| 46 static GtkWidget *cfgdlg; | 47 static GtkWidget *cfgdlg; |
| 48 static gboolean submit; | |
| 47 | 49 |
| 48 static GMutex *m_scrobbler; | 50 static GMutex *m_scrobbler; |
| 49 static GThread *pt_scrobbler; | 51 static GThread *pt_scrobbler; |
| 50 static GThread *pt_handshake; | 52 static GThread *pt_handshake; |
| 51 | 53 |
| 61 init, | 63 init, |
| 62 about_show, | 64 about_show, |
| 63 NULL, | 65 NULL, |
| 64 cleanup | 66 cleanup |
| 65 }; | 67 }; |
| 68 | |
| 69 static gboolean ishttp(const char *a) | |
| 70 { | |
| 71 g_return_val_if_fail(a != NULL, FALSE); | |
| 72 return str_has_prefix_nocase(a, "http://") || str_has_prefix_nocase(a, "https://"); | |
| 73 } | |
| 74 | |
| 75 static void hook_playback_begin(PlaylistEntry *entry) | |
| 76 { | |
| 77 g_return_if_fail(entry != NULL); | |
| 78 | |
| 79 if (entry->length < 30) | |
| 80 { | |
| 81 pdebug(" *** not submitting due to entry->length < 30", DEBUG); | |
| 82 return; | |
| 83 } | |
| 84 | |
| 85 if (ishttp(entry->filename)) | |
| 86 { | |
| 87 pdebug(" *** not submitting due to HTTP source", DEBUG); | |
| 88 return; | |
| 89 } | |
| 90 | |
| 91 /* wake up the scrobbler thread to submit or queue */ | |
| 92 submit = TRUE; | |
| 93 g_cond_signal(xs_cond); | |
| 94 } | |
| 66 | 95 |
| 67 static void init(void) | 96 static void init(void) |
| 68 { | 97 { |
| 69 char *username = NULL, *password = NULL; | 98 char *username = NULL, *password = NULL; |
| 70 char *ge_username = NULL, *ge_password = NULL; | 99 char *ge_username = NULL, *ge_password = NULL; |
| 141 pdebug("plugin started", DEBUG); | 170 pdebug("plugin started", DEBUG); |
| 142 } | 171 } |
| 143 | 172 |
| 144 static void cleanup(void) | 173 static void cleanup(void) |
| 145 { | 174 { |
| 146 g_free (xmms_scrobbler.description); | 175 g_free (xmms_scrobbler.description); |
| 147 xmms_scrobbler.description = NULL; | 176 xmms_scrobbler.description = NULL; |
| 148 | 177 |
| 149 prefswin_page_destroy(cfgdlg); | 178 prefswin_page_destroy(cfgdlg); |
| 150 | 179 |
| 151 if (!sc_going && !ge_going) | 180 if (!sc_going && !ge_going) |
| 152 return; | 181 return; |
| 177 | 206 |
| 178 sc_cleaner(); | 207 sc_cleaner(); |
| 179 gerpok_sc_cleaner(); | 208 gerpok_sc_cleaner(); |
| 180 } | 209 } |
| 181 | 210 |
| 182 static gboolean ishttp(const char *a) | |
| 183 { | |
| 184 g_return_val_if_fail(a != NULL, FALSE); | |
| 185 return str_has_prefix_nocase(a, "http://"); | |
| 186 } | |
| 187 | |
| 188 /* Following code thanks to nosuke | |
| 189 * | |
| 190 * It should probably be cleaned up | |
| 191 */ | |
| 192 static submit_t get_song_status(void) | |
| 193 { | |
| 194 static int pos_c, playlistlen_c, playtime_c, time_c, | |
| 195 pos_p = 0, playlistlen_p = 0, playtime_p = 0, | |
| 196 playtime_i = 0, time_i = 0, | |
| 197 playtime_ofs = 0; | |
| 198 static char *file_c = NULL, *file_p = NULL; | |
| 199 Playlist *playlist = playlist_get_active(); | |
| 200 | |
| 201 static enum playstatus { | |
| 202 ps_stop, ps_play, ps_pause | |
| 203 } ps_c, ps_p = ps_stop; | |
| 204 | |
| 205 static int submitted = 0, changed, seeked, repeat, | |
| 206 filechanged, rewind, len = 0; | |
| 207 | |
| 208 static enum state { | |
| 209 start, stop, pause, restart, playing, pausing, stopping | |
| 210 } playstate; | |
| 211 | |
| 212 submit_t dosubmit; | |
| 213 | |
| 214 struct timeval timetmp; | |
| 215 | |
| 216 /* clear dosubmit */ | |
| 217 dosubmit.dosubmit = dosubmit.pos_c = dosubmit.len = dosubmit.gerpok = 0; | |
| 218 | |
| 219 /* make sure playlist != NULL */ | |
| 220 if (!playlist) | |
| 221 return dosubmit; | |
| 222 | |
| 223 /* current music number */ | |
| 224 pos_c = playlist_get_position(playlist); | |
| 225 /* current file name */ | |
| 226 file_c = playlist_get_filename(playlist, pos_c); | |
| 227 | |
| 228 if ((file_c != NULL) && (ishttp(file_c))) | |
| 229 return dosubmit; | |
| 230 | |
| 231 /* total number */ | |
| 232 playlistlen_c = playlist_get_length(playlist); | |
| 233 /* current playtime */ | |
| 234 playtime_c = audacious_drct_get_time(); | |
| 235 /* total length */ | |
| 236 len = playlist_get_songtime(playlist, pos_c); | |
| 237 | |
| 238 /* current time (ms) */ | |
| 239 gettimeofday(&timetmp, NULL); | |
| 240 time_c = timetmp.tv_sec * 1000 + timetmp.tv_usec / 1000; | |
| 241 | |
| 242 /* current status */ | |
| 243 if( audacious_drct_get_paused() ) { | |
| 244 ps_c = ps_pause; | |
| 245 }else if( audacious_drct_get_playing() ) { | |
| 246 ps_c = ps_play; | |
| 247 }else{ | |
| 248 ps_c = ps_stop; | |
| 249 } | |
| 250 | |
| 251 /* repeat setting */ | |
| 252 repeat = cfg.repeat; | |
| 253 | |
| 254 if( ps_p == ps_stop && ps_c == ps_stop ) playstate = stopping; | |
| 255 else if( ps_p == ps_stop && ps_c == ps_play ) playstate = start; | |
| 256 /* else if( ps_p == ps_stop && ps_c == ps_pause ) ; */ | |
| 257 else if( ps_p == ps_play && ps_c == ps_play ) playstate = playing; | |
| 258 else if( ps_p == ps_play && ps_c == ps_stop ) playstate = stop; | |
| 259 else if( ps_p == ps_play && ps_c == ps_pause ) playstate = pause; | |
| 260 else if( ps_p == ps_pause && ps_c == ps_pause ) playstate = pausing; | |
| 261 else if( ps_p == ps_pause && ps_c == ps_play ) playstate = restart; | |
| 262 else if( ps_p == ps_pause && ps_c == ps_stop ) playstate = stop; | |
| 263 else playstate = stopping; | |
| 264 | |
| 265 /* filename has changed */ | |
| 266 if( !(file_p == NULL && file_c == NULL) && | |
| 267 ((file_p == NULL && file_c != NULL) || | |
| 268 (file_p != NULL && file_c == NULL) || | |
| 269 (file_p != NULL && file_c != NULL && strcmp(file_c, file_p))) ){ | |
| 270 filechanged = 1; | |
| 271 pdebug("*** filechange ***", SUB_DEBUG); | |
| 272 }else{ | |
| 273 filechanged = 0; | |
| 274 } | |
| 275 if( file_c == NULL ){ len = 0; } | |
| 276 | |
| 277 /* whole rewind has occurred (maybe) */ | |
| 278 if( len != 0 && len - (playtime_p - playtime_c) < 3000 ){ | |
| 279 rewind = 1; | |
| 280 pdebug("*** rewind ***", SUB_DEBUG); | |
| 281 }else{ | |
| 282 rewind = 0; | |
| 283 } | |
| 284 | |
| 285 | |
| 286 changed = 0; | |
| 287 seeked = 0; | |
| 288 | |
| 289 switch( playstate ){ | |
| 290 case start: | |
| 291 pdebug("*** START ***", SUB_DEBUG); | |
| 292 dosubmit.gerpok = 1; | |
| 293 dosubmit.len = len; | |
| 294 break; | |
| 295 case stop: | |
| 296 pdebug("*** STOP ***", SUB_DEBUG); | |
| 297 len = 0; | |
| 298 break; | |
| 299 case pause: | |
| 300 pdebug("*** PAUSE ***", SUB_DEBUG); | |
| 301 playtime_ofs += playtime_c - playtime_i; /* save playtime */ | |
| 302 break; | |
| 303 case restart: | |
| 304 pdebug("*** RESTART ***", SUB_DEBUG); | |
| 305 playtime_i = playtime_c; /* restore playtime */ | |
| 306 break; | |
| 307 case playing: | |
| 308 if( (playtime_c < playtime_p) || /* back */ | |
| 309 ( (playtime_c - playtime_i) - (time_c - time_i) > 3000 ) | |
| 310 /* forward */ | |
| 311 ) { | |
| 312 seeked = 1; | |
| 313 } | |
| 314 | |
| 315 if( filechanged || /* filename has changed */ | |
| 316 ( !filechanged && /* filename has not changed... */ | |
| 317 /* (( rewind && (repeat && (!advance || | |
| 318 (pos_c == 0 && playlistlen_c == 1 )))) || */ | |
| 319 /* looping with only one file */ | |
| 320 (( pos_c == 0 && playlistlen_c == 1 && repeat | |
| 321 && rewind ) || | |
| 322 /* looping? */ | |
| 323 ( pos_p == pos_c && rewind ) || | |
| 324 | |
| 325 ( pos_p != pos_c && seeked ) || | |
| 326 /* skip from current music to next music, | |
| 327 which has the same filename as previous one */ | |
| 328 ( pos_p < pos_c && playtime_c < playtime_p ) || | |
| 329 /* current song has removed from playlist | |
| 330 but the next (following) song has the same | |
| 331 filename */ | |
| 332 ( playlistlen_p > playlistlen_c | |
| 333 && playtime_c < playtime_p )))){ | |
| 334 pdebug("*** CHANGE ***",SUB_DEBUG); | |
| 335 pdebug(fmt_vastr(" filechanged = %d",filechanged),SUB_DEBUG); | |
| 336 pdebug(fmt_vastr(" pos_c = %d",pos_c),SUB_DEBUG); | |
| 337 pdebug(fmt_vastr(" pos_p = %d",pos_p),SUB_DEBUG); | |
| 338 pdebug(fmt_vastr(" rewind = %d", rewind),SUB_DEBUG); | |
| 339 pdebug(fmt_vastr(" seeked = %d", seeked),SUB_DEBUG); | |
| 340 pdebug(fmt_vastr(" playtime_c = %d", playtime_c),SUB_DEBUG); | |
| 341 pdebug(fmt_vastr(" playtime_p = %d", playtime_p),SUB_DEBUG); | |
| 342 pdebug(fmt_vastr(" playlistlen_c = %d", playlistlen_p), | |
| 343 SUB_DEBUG); | |
| 344 pdebug(fmt_vastr(" playlistlen_p = %d", playlistlen_p), | |
| 345 SUB_DEBUG); | |
| 346 changed = 1; | |
| 347 seeked = 0; | |
| 348 | |
| 349 if (file_p != NULL) | |
| 350 { | |
| 351 g_free(file_p); | |
| 352 file_p = NULL; | |
| 353 } | |
| 354 }else if( seeked ) { | |
| 355 seeked = 1; | |
| 356 pdebug("*** SEEK ***", SUB_DEBUG); | |
| 357 } | |
| 358 | |
| 359 break; | |
| 360 case pausing: | |
| 361 if(playtime_c != playtime_p){ | |
| 362 pdebug("*** SEEK ***", SUB_DEBUG); | |
| 363 seeked = 1; | |
| 364 } | |
| 365 break; | |
| 366 case stopping: | |
| 367 len = 0; | |
| 368 break; | |
| 369 default: | |
| 370 pdebug("*** unknown state tranfer!!! ***", SUB_DEBUG); | |
| 371 break; | |
| 372 } | |
| 373 | |
| 374 | |
| 375 if( playstate == start || changed || (seeked && !submitted) ){ | |
| 376 /* reset counter */ | |
| 377 pdebug(" <<< reset counter >>>", SUB_DEBUG); | |
| 378 | |
| 379 submitted = 0; | |
| 380 playtime_ofs = 0; | |
| 381 playtime_i = playtime_c; | |
| 382 time_i = time_c; | |
| 383 | |
| 384 }else{ | |
| 385 /* check playtime for submitting */ | |
| 386 if( !submitted ){ | |
| 387 if( len > 30 * 1000 && | |
| 388 /* len < 30 *60 * 1000 && // crazy rule!!! */ | |
| 389 ( | |
| 390 (playtime_ofs + playtime_c - playtime_i > len / 2) || | |
| 391 (playtime_ofs + playtime_c - playtime_i > 240 * 1000) | |
| 392 /* (playtime_c - playtime_i > 10 * 1000)// for debug */ | |
| 393 )){ | |
| 394 pdebug("*** submitting requirements are satisfied.", | |
| 395 SUB_DEBUG); | |
| 396 pdebug(fmt_vastr(" len = %d, playtime = %d", | |
| 397 len / 1000, (playtime_c - playtime_i)/1000 ), | |
| 398 SUB_DEBUG); | |
| 399 submitted = 1; | |
| 400 dosubmit.dosubmit = 1; | |
| 401 dosubmit.pos_c = pos_c; | |
| 402 dosubmit.len = len; | |
| 403 } | |
| 404 } | |
| 405 } | |
| 406 | |
| 407 if (playstate != start) | |
| 408 dosubmit.gerpok = 0; | |
| 409 | |
| 410 g_free(file_p); | |
| 411 | |
| 412 /* keep current value for next iteration */ | |
| 413 ps_p = ps_c; | |
| 414 file_p = file_c; | |
| 415 playtime_p = playtime_c; | |
| 416 pos_p = pos_c; | |
| 417 playlistlen_p = playlistlen_c; | |
| 418 | |
| 419 return dosubmit; | |
| 420 } | |
| 421 | |
| 422 static void *xs_thread(void *data __attribute__((unused))) | 211 static void *xs_thread(void *data __attribute__((unused))) |
| 423 { | 212 { |
| 424 int run = 1; | 213 int run = 1; |
| 425 submit_t dosubmit; | |
| 426 GTimeVal sleeptime; | |
| 427 | 214 |
| 428 while (run) { | 215 while (run) { |
| 216 TitleInput *tuple; | |
| 217 GTimeVal sleeptime; | |
| 218 | |
| 429 /* Error catching */ | 219 /* Error catching */ |
| 430 if(sc_catch_error()) | 220 if(sc_catch_error()) |
| 431 { | 221 { |
| 432 errorbox_show(sc_fetch_error()); | 222 errorbox_show(sc_fetch_error()); |
| 433 sc_clear_error(); | 223 sc_clear_error(); |
| 437 { | 227 { |
| 438 errorbox_show(gerpok_sc_fetch_error()); | 228 errorbox_show(gerpok_sc_fetch_error()); |
| 439 gerpok_sc_clear_error(); | 229 gerpok_sc_clear_error(); |
| 440 } | 230 } |
| 441 | 231 |
| 442 /* Check for ability to submit */ | 232 if (submit) |
| 443 dosubmit = get_song_status(); | 233 { |
| 444 | 234 Playlist *playlist; |
| 445 if(dosubmit.gerpok) { | |
| 446 TitleInput *tuple; | |
| 447 | 235 |
| 448 pdebug("Submitting song.", DEBUG); | 236 pdebug("Submitting song.", DEBUG); |
| 449 | 237 |
| 450 tuple = playlist_get_tuple(playlist_get_active(), dosubmit.pos_c); | 238 playlist = playlist_get_active(); |
| 239 tuple = playlist_get_tuple(playlist, playlist_get_position(playlist)); | |
| 451 | 240 |
| 452 if (tuple == NULL) | 241 if (tuple == NULL) |
| 453 continue; | 242 continue; |
| 454 | 243 |
| 455 if (ishttp(tuple->file_name)) | 244 if (ishttp(tuple->file_name)) |
| 458 if(tuple->performer != NULL && tuple->track_name != NULL) | 247 if(tuple->performer != NULL && tuple->track_name != NULL) |
| 459 { | 248 { |
| 460 pdebug(fmt_vastr( | 249 pdebug(fmt_vastr( |
| 461 "submitting artist: %s, title: %s", | 250 "submitting artist: %s, title: %s", |
| 462 tuple->performer, tuple->track_name), DEBUG); | 251 tuple->performer, tuple->track_name), DEBUG); |
| 463 gerpok_sc_addentry(m_scrobbler, tuple, | 252 sc_addentry(m_scrobbler, tuple, tuple->length / 1000); |
| 464 dosubmit.len/1000); | 253 gerpok_sc_addentry(m_scrobbler, tuple, tuple->length / 1000); |
| 465 } | |
| 466 else | |
| 467 pdebug("tuple does not contain an artist or a title, not submitting.", DEBUG); | |
| 468 } | |
| 469 | |
| 470 if(dosubmit.dosubmit) { | |
| 471 TitleInput *tuple; | |
| 472 | |
| 473 pdebug("Submitting song.", DEBUG); | |
| 474 | |
| 475 tuple = playlist_get_tuple(playlist_get_active(), dosubmit.pos_c); | |
| 476 | |
| 477 if (tuple == NULL) | |
| 478 continue; | |
| 479 | |
| 480 if (ishttp(tuple->file_name)) | |
| 481 continue; | |
| 482 | |
| 483 if(tuple->performer != NULL && tuple->track_name != NULL) | |
| 484 { | |
| 485 pdebug(fmt_vastr( | |
| 486 "submitting artist: %s, title: %s", | |
| 487 tuple->performer, tuple->track_name), DEBUG); | |
| 488 sc_addentry(m_scrobbler, tuple, | |
| 489 dosubmit.len/1000); | |
| 490 } | 254 } |
| 491 else | 255 else |
| 492 pdebug("tuple does not contain an artist or a title, not submitting.", DEBUG); | 256 pdebug("tuple does not contain an artist or a title, not submitting.", DEBUG); |
| 493 } | 257 |
| 258 submit = FALSE; | |
| 259 } | |
| 260 | |
| 494 g_get_current_time(&sleeptime); | 261 g_get_current_time(&sleeptime); |
| 495 sleeptime.tv_sec += XS_SLEEP; | 262 sleeptime.tv_sec += XS_SLEEP; |
| 263 | |
| 264 g_mutex_lock(xs_mutex); | |
| 265 g_cond_timed_wait(xs_cond, xs_mutex, &sleeptime); | |
| 266 g_mutex_unlock(xs_mutex); | |
| 496 | 267 |
| 497 g_mutex_lock(m_scrobbler); | 268 g_mutex_lock(m_scrobbler); |
| 498 run = (sc_going != 0 || ge_going != 0); | 269 run = (sc_going != 0 || ge_going != 0); |
| 499 g_mutex_unlock(m_scrobbler); | 270 g_mutex_unlock(m_scrobbler); |
| 500 | |
| 501 g_mutex_lock(xs_mutex); | |
| 502 g_cond_timed_wait(xs_cond, xs_mutex, &sleeptime); | |
| 503 g_mutex_unlock(xs_mutex); | |
| 504 } | 271 } |
| 505 pdebug("scrobbler thread: exiting", DEBUG); | 272 pdebug("scrobbler thread: exiting", DEBUG); |
| 506 g_thread_exit(NULL); | 273 g_thread_exit(NULL); |
| 507 | 274 |
| 508 return NULL; | 275 return NULL; |
