1 /*****************************************************************************
2 * audioscrobbler.c : audioscrobbler submission plugin
3 *****************************************************************************
4 * Copyright © 2006-2011 the VideoLAN team
7 * Author: Rafaël Carré <funman at videolanorg>
8 * Ilkka Ollakka <ileoo at videolan org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 /* audioscrobbler protocol version: 1.2
26 * http://www.audioscrobbler.net/development/protocol/
28 * TODO: "Now Playing" feature (not mandatory)
29 * Update to new API? http://www.lastfm.fr/api
31 /*****************************************************************************
33 *****************************************************************************/
45 #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
46 #include <vlc_common.h>
47 #include <vlc_plugin.h>
48 #include <vlc_interface.h>
49 #include <vlc_input.h>
50 #include <vlc_dialog.h>
53 #include <vlc_stream.h>
55 #include <vlc_network.h>
56 #include <vlc_playlist.h>
58 /*****************************************************************************
60 *****************************************************************************/
64 /* Keeps track of metadata to be submitted */
65 typedef struct audioscrobbler_song_t
67 char *psz_a; /**< track artist */
68 char *psz_t; /**< track title */
69 char *psz_b; /**< track album */
70 char *psz_n; /**< track number */
71 int i_l; /**< track length */
72 char *psz_m; /**< musicbrainz id */
73 time_t date; /**< date since epoch */
74 mtime_t i_start; /**< playing start */
75 } audioscrobbler_song_t;
79 audioscrobbler_song_t p_queue[QUEUE_MAX]; /**< songs not submitted yet*/
80 int i_songs; /**< number of songs */
82 input_thread_t *p_input; /**< current input thread */
83 vlc_mutex_t lock; /**< p_sys mutex */
84 vlc_cond_t wait; /**< song to submit event */
85 vlc_thread_t thread; /**< thread to submit song */
87 /* submission of played songs */
88 vlc_url_t p_submit_url; /**< where to submit data */
90 /* submission of playing song */
92 char *psz_nowp_host; /**< where to submit data */
93 int i_nowp_port; /**< port to which submit */
94 char *psz_nowp_file; /**< file to which submit */
96 char psz_auth_token[33]; /**< Authentication token */
98 /* data about song currently playing */
99 audioscrobbler_song_t p_current_song; /**< song being played */
101 mtime_t time_pause; /**< time when vlc paused */
102 mtime_t time_total_pauses; /**< total time in pause */
104 bool b_submit; /**< do we have to submit ? */
106 bool b_meta_read; /**< if we read the song's
107 * metadata already */
110 static int Open (vlc_object_t *);
111 static void Close (vlc_object_t *);
112 static void *Run (void *);
114 /*****************************************************************************
116 ****************************************************************************/
118 #define USERNAME_TEXT N_("Username")
119 #define USERNAME_LONGTEXT N_("The username of your last.fm account")
120 #define PASSWORD_TEXT N_("Password")
121 #define PASSWORD_LONGTEXT N_("The password of your last.fm account")
122 #define URL_TEXT N_("Scrobbler URL")
123 #define URL_LONGTEXT N_("The URL set for an alternative scrobbler engine")
125 /* This error value is used when last.fm plugin has to be unloaded. */
126 #define VLC_AUDIOSCROBBLER_EFATAL -69
128 /* last.fm client identifier */
129 #define CLIENT_NAME PACKAGE
130 #define CLIENT_VERSION VERSION
133 set_category(CAT_INTERFACE)
134 set_subcategory(SUBCAT_INTERFACE_CONTROL)
135 set_shortname(N_("Audioscrobbler"))
136 set_description(N_("Submission of played songs to last.fm"))
137 add_string("lastfm-username", "",
138 USERNAME_TEXT, USERNAME_LONGTEXT, false)
139 add_password("lastfm-password", "",
140 PASSWORD_TEXT, PASSWORD_LONGTEXT, false)
141 add_string("scrobbler-url", "post.audioscrobbler.com",
142 URL_TEXT, URL_LONGTEXT, false)
143 set_capability("interface", 0)
144 set_callbacks(Open, Close)
147 /*****************************************************************************
148 * DeleteSong : Delete the char pointers in a song
149 *****************************************************************************/
150 static void DeleteSong(audioscrobbler_song_t* p_song)
152 FREENULL(p_song->psz_a);
153 FREENULL(p_song->psz_b);
154 FREENULL(p_song->psz_t);
155 FREENULL(p_song->psz_m);
156 FREENULL(p_song->psz_n);
159 /*****************************************************************************
160 * ReadMetaData : Read meta data when parsed by vlc
161 *****************************************************************************/
162 static void ReadMetaData(intf_thread_t *p_this, input_thread_t *p_input)
164 intf_sys_t *p_sys = p_this->p_sys;
166 assert(p_input != NULL);
168 input_item_t *p_item = input_GetItem(p_input);
172 #define ALLOC_ITEM_META(a, b) do { \
173 char *psz_meta = input_item_Get##b(p_item); \
174 if (psz_meta && *psz_meta) \
175 a = encode_URI_component(psz_meta); \
179 vlc_mutex_lock(&p_sys->lock);
181 p_sys->b_meta_read = true;
183 ALLOC_ITEM_META(p_sys->p_current_song.psz_a, Artist);
184 if (!p_sys->p_current_song.psz_a)
186 msg_Dbg(p_this, "No artist..");
187 DeleteSong(&p_sys->p_current_song);
191 ALLOC_ITEM_META(p_sys->p_current_song.psz_t, Title);
192 if (!p_sys->p_current_song.psz_t)
194 msg_Dbg(p_this, "No track name..");
195 DeleteSong(&p_sys->p_current_song);
199 /* Now we have read the mandatory meta data, so we can submit that info */
200 p_sys->b_submit = true;
202 ALLOC_ITEM_META(p_sys->p_current_song.psz_b, Album);
203 if (!p_sys->p_current_song.psz_b)
204 p_sys->p_current_song.psz_b = calloc(1, 1);
206 ALLOC_ITEM_META(p_sys->p_current_song.psz_m, TrackID);
207 if (!p_sys->p_current_song.psz_m)
208 p_sys->p_current_song.psz_m = calloc(1, 1);
210 p_sys->p_current_song.i_l = input_item_GetDuration(p_item) / 1000000;
212 ALLOC_ITEM_META(p_sys->p_current_song.psz_n, TrackNum);
213 if (!p_sys->p_current_song.psz_n)
214 p_sys->p_current_song.psz_n = calloc(1, 1);
215 #undef ALLOC_ITEM_META
217 msg_Dbg(p_this, "Meta data registered");
220 vlc_mutex_unlock(&p_sys->lock);
223 /*****************************************************************************
224 * AddToQueue: Add the played song to the queue to be submitted
225 *****************************************************************************/
226 static void AddToQueue (intf_thread_t *p_this)
229 intf_sys_t *p_sys = p_this->p_sys;
231 vlc_mutex_lock(&p_sys->lock);
232 if (!p_sys->b_submit)
235 /* wait for the user to listen enough before submitting */
236 played_time = mdate() - p_sys->p_current_song.i_start -
237 p_sys->time_total_pauses;
238 played_time /= 1000000; /* µs → s */
240 /*HACK: it seam that the preparsing sometime fail,
241 so use the playing time as the song length */
242 if (p_sys->p_current_song.i_l == 0)
243 p_sys->p_current_song.i_l = played_time;
245 /* Don't send song shorter than 30s */
246 if (p_sys->p_current_song.i_l < 30)
248 msg_Dbg(p_this, "Song too short (< 30s), not submitting");
252 /* Send if the user had listen more than 240s OR half the track length */
253 if ((played_time < 240) &&
254 (played_time < (p_sys->p_current_song.i_l / 2)))
256 msg_Dbg(p_this, "Song not listened long enough, not submitting");
260 /* Check that all meta are present */
261 if (!p_sys->p_current_song.psz_a || !*p_sys->p_current_song.psz_a ||
262 !p_sys->p_current_song.psz_t || !*p_sys->p_current_song.psz_t)
264 msg_Dbg(p_this, "Missing artist or title, not submitting");
268 if (p_sys->i_songs >= QUEUE_MAX)
270 msg_Warn(p_this, "Submission queue is full, not submitting");
274 msg_Dbg(p_this, "Song will be submitted.");
276 #define QUEUE_COPY(a) \
277 p_sys->p_queue[p_sys->i_songs].a = p_sys->p_current_song.a
279 #define QUEUE_COPY_NULL(a) \
281 p_sys->p_current_song.a = NULL
284 QUEUE_COPY_NULL(psz_n);
285 QUEUE_COPY_NULL(psz_a);
286 QUEUE_COPY_NULL(psz_t);
287 QUEUE_COPY_NULL(psz_b);
288 QUEUE_COPY_NULL(psz_m);
290 #undef QUEUE_COPY_NULL
295 /* signal the main loop we have something to submit */
296 vlc_cond_signal(&p_sys->wait);
299 DeleteSong(&p_sys->p_current_song);
300 p_sys->b_submit = false;
301 vlc_mutex_unlock(&p_sys->lock);
304 /*****************************************************************************
305 * PlayingChange: Playing status change callback
306 *****************************************************************************/
307 static int PlayingChange(vlc_object_t *p_this, const char *psz_var,
308 vlc_value_t oldval, vlc_value_t newval, void *p_data)
312 intf_thread_t *p_intf = (intf_thread_t*) p_data;
313 intf_sys_t *p_sys = p_intf->p_sys;
314 input_thread_t *p_input = (input_thread_t*)p_this;
319 if (newval.i_int != INPUT_EVENT_STATE) return VLC_SUCCESS;
321 if (var_CountChoices(p_input, "video-es"))
323 msg_Dbg(p_this, "Not an audio-only input, not submitting");
327 state = var_GetInteger(p_input, "state");
329 if (!p_sys->b_meta_read && state >= PLAYING_S)
331 ReadMetaData(p_intf, p_input);
338 else if (state == PAUSE_S)
339 p_sys->time_pause = mdate();
340 else if (p_sys->time_pause > 0 && state == PLAYING_S)
342 p_sys->time_total_pauses += (mdate() - p_sys->time_pause);
343 p_sys->time_pause = 0;
349 /*****************************************************************************
350 * ItemChange: Playlist item change callback
351 *****************************************************************************/
352 static int ItemChange(vlc_object_t *p_this, const char *psz_var,
353 vlc_value_t oldval, vlc_value_t newval, void *p_data)
355 intf_thread_t *p_intf = p_data;
356 intf_sys_t *p_sys = p_intf->p_sys;
357 input_thread_t *p_input = newval.p_address;
359 VLC_UNUSED(p_this); VLC_UNUSED(psz_var);
362 p_sys->b_meta_read = false;
363 p_sys->b_submit = false;
365 if (p_sys->p_input != NULL)
367 var_DelCallback(p_sys->p_input, "intf-event", PlayingChange, p_intf);
368 vlc_object_release(p_sys->p_input);
369 p_sys->p_input = NULL;
375 input_item_t *p_item = input_GetItem(p_input);
379 if (var_CountChoices(p_input, "video-es"))
381 msg_Dbg(p_this, "Not an audio-only input, not submitting");
385 p_sys->time_total_pauses = 0;
386 time(&p_sys->p_current_song.date); /* to be sent to last.fm */
387 p_sys->p_current_song.i_start = mdate(); /* only used locally */
389 p_sys->p_input = vlc_object_hold(p_input);
390 var_AddCallback(p_input, "intf-event", PlayingChange, p_intf);
392 if (input_item_IsPreparsed(p_item))
393 ReadMetaData(p_intf, p_input);
394 /* if the input item was not preparsed, we'll do it in PlayingChange()
395 * callback, when "state" == PLAYING_S */
400 /*****************************************************************************
401 * Open: initialize and create stuff
402 *****************************************************************************/
403 static int Open(vlc_object_t *p_this)
405 intf_thread_t *p_intf = (intf_thread_t*) p_this;
406 intf_sys_t *p_sys = calloc(1, sizeof(intf_sys_t));
411 p_intf->p_sys = p_sys;
413 vlc_mutex_init(&p_sys->lock);
414 vlc_cond_init(&p_sys->wait);
416 if (vlc_clone(&p_sys->thread, Run, p_intf, VLC_THREAD_PRIORITY_LOW))
418 vlc_cond_destroy(&p_sys->wait);
419 vlc_mutex_destroy(&p_sys->lock);
424 var_AddCallback(pl_Get(p_intf), "input-current", ItemChange, p_intf);
429 /*****************************************************************************
430 * Close: destroy interface stuff
431 *****************************************************************************/
432 static void Close(vlc_object_t *p_this)
434 intf_thread_t *p_intf = (intf_thread_t*) p_this;
435 intf_sys_t *p_sys = p_intf->p_sys;
437 vlc_cancel(p_sys->thread);
438 vlc_join(p_sys->thread, NULL);
440 var_DelCallback(pl_Get(p_intf), "input-current", ItemChange, p_intf);
442 if (p_sys->p_input != NULL)
444 var_DelCallback(p_sys->p_input, "intf-event", PlayingChange, p_intf);
445 vlc_object_release(p_sys->p_input);
449 for (i = 0; i < p_sys->i_songs; i++)
450 DeleteSong(&p_sys->p_queue[i]);
451 vlc_UrlClean(&p_sys->p_submit_url);
453 free(p_sys->psz_nowp_host);
454 free(p_sys->psz_nowp_file);
456 vlc_cond_destroy(&p_sys->wait);
457 vlc_mutex_destroy(&p_sys->lock);
461 /*****************************************************************************
462 * Handshake : Init audioscrobbler connection
463 *****************************************************************************/
464 static int Handshake(intf_thread_t *p_this)
466 char *psz_username, *psz_password;
467 char *psz_scrobbler_url;
469 char psz_timestamp[21];
471 struct md5_s p_struct_md5;
474 char *psz_handshake_url;
475 uint8_t p_buffer[1024];
481 intf_thread_t *p_intf = (intf_thread_t*) p_this;
482 intf_sys_t *p_sys = p_this->p_sys;
484 psz_username = var_InheritString(p_this, "lastfm-username");
485 psz_password = var_InheritString(p_this, "lastfm-password");
487 /* username or password have not been setup */
488 if (EMPTY_STR(psz_username) || EMPTY_STR(psz_password))
497 /* generates a md5 hash of the password */
498 InitMD5(&p_struct_md5);
499 AddMD5(&p_struct_md5, (uint8_t*) psz_password, strlen(psz_password));
500 EndMD5(&p_struct_md5);
504 char *psz_password_md5 = psz_md5_hash(&p_struct_md5);
505 if (!psz_password_md5)
511 snprintf(psz_timestamp, sizeof(psz_timestamp), "%"PRIu64,
512 (uint64_t)timestamp);
514 /* generates a md5 hash of :
515 * - md5 hash of the password, plus
516 * - timestamp in clear text
518 InitMD5(&p_struct_md5);
519 AddMD5(&p_struct_md5, (uint8_t*) psz_password_md5, 32);
520 AddMD5(&p_struct_md5, (uint8_t*) psz_timestamp, strlen(psz_timestamp));
521 EndMD5(&p_struct_md5);
522 free(psz_password_md5);
524 char *psz_auth_token = psz_md5_hash(&p_struct_md5);
531 psz_scrobbler_url = var_InheritString(p_this, "scrobbler-url");
532 if (!psz_scrobbler_url)
534 free(psz_auth_token);
539 i_ret = asprintf(&psz_handshake_url,
540 "http://%s/?hs=true&p=1.2&c="CLIENT_NAME"&v="CLIENT_VERSION"&u=%s&t=%s&a=%s"
541 , psz_scrobbler_url, psz_username, psz_timestamp, psz_auth_token);
543 free(psz_auth_token);
544 free(psz_scrobbler_url);
549 /* send the http handshake request */
550 p_stream = stream_UrlNew(p_intf, psz_handshake_url);
551 free(psz_handshake_url);
557 i_ret = stream_Read(p_stream, p_buffer, sizeof(p_buffer) - 1);
560 stream_Delete(p_stream);
563 p_buffer[i_ret] = '\0';
564 stream_Delete(p_stream);
566 p_buffer_pos = strstr((char*) p_buffer, "FAILED ");
569 /* handshake request failed, sorry */
570 msg_Err(p_this, "last.fm handshake failed: %s", p_buffer_pos + 7);
574 if (strstr((char*) p_buffer, "BADAUTH"))
576 /* authentication failed, bad username/password combination */
578 _("last.fm: Authentication failed"),
579 "%s", _("last.fm username or password is incorrect. "
580 "Please verify your settings and relaunch VLC."));
581 return VLC_AUDIOSCROBBLER_EFATAL;
584 if (strstr((char*) p_buffer, "BANNED"))
586 /* oops, our version of vlc has been banned by last.fm servers */
587 msg_Err(p_intf, "This version of VLC has been banned by last.fm. "
588 "You should upgrade VLC, or disable the last.fm plugin.");
589 return VLC_AUDIOSCROBBLER_EFATAL;
592 if (strstr((char*) p_buffer, "BADTIME"))
594 /* The system clock isn't good */
595 msg_Err(p_intf, "last.fm handshake failed because your clock is too "
596 "much shifted. Please correct it, and relaunch VLC.");
597 return VLC_AUDIOSCROBBLER_EFATAL;
600 p_buffer_pos = strstr((char*) p_buffer, "OK");
604 p_buffer_pos = strstr(p_buffer_pos, "\n");
605 if (!p_buffer_pos || strlen(p_buffer_pos) < 33)
607 p_buffer_pos++; /* we skip the '\n' */
609 /* save the session ID */
610 memcpy(p_sys->psz_auth_token, p_buffer_pos, 32);
611 p_sys->psz_auth_token[32] = '\0';
613 p_buffer_pos = strstr(p_buffer_pos, "http://");
614 if (!p_buffer_pos || strlen(p_buffer_pos) == 7)
617 /* We need to read the nowplaying url */
618 p_buffer_pos += 7; /* we skip "http://" */
620 psz_url = strndup(p_buffer_pos, strcspn(p_buffer_pos, "\n"));
624 switch(ParseURL(psz_url, &p_sys->psz_nowp_host,
625 &p_sys->psz_nowp_file, &p_sys->i_nowp_port))
636 p_buffer_pos = strstr(p_buffer_pos, "http://");
637 if (!p_buffer_pos || strlen(p_buffer_pos) == 7)
640 /* We need to read the submission url */
641 p_buffer_pos += 7; /* we skip "http://" */
642 psz_url = strndup(p_buffer_pos, strcspn(p_buffer_pos, "\n"));
646 /* parse the submission url */
647 vlc_UrlParse(&p_sys->p_submit_url, psz_url);
656 msg_Err(p_intf, "Handshake: can't recognize server protocol");
660 static void HandleInterval(mtime_t *next, unsigned int *i_interval)
662 if (*i_interval == 0)
664 /* first interval is 1 minute */
669 /* else we double the previous interval, up to 120 minutes */
671 if (*i_interval > 120)
674 *next = mdate() + (*i_interval * 1000000 * 60);
677 /*****************************************************************************
678 * Run : call Handshake() then submit songs
679 *****************************************************************************/
680 static void *Run(void *data)
682 intf_thread_t *p_intf = data;
683 uint8_t p_buffer[1024];
684 int canc = vlc_savecancel();
685 bool b_handshaked = false;
687 /* data about audioscrobbler session */
688 mtime_t next_exchange = 0; /**< when can we send data */
689 unsigned int i_interval = 0; /**< waiting interval (secs)*/
691 intf_sys_t *p_sys = p_intf->p_sys;
696 vlc_restorecancel(canc);
697 mwait(next_exchange);
699 vlc_mutex_lock(&p_sys->lock);
700 mutex_cleanup_push(&p_sys->lock);
702 while (p_sys->i_songs == 0)
703 vlc_cond_wait(&p_sys->wait, &p_sys->lock);
706 vlc_mutex_unlock(&p_sys->lock);
707 canc = vlc_savecancel();
709 /* handshake if needed */
712 msg_Dbg(p_intf, "Handshaking with last.fm ...");
714 switch(Handshake(p_intf))
720 /* username not set */
722 _("Last.fm username not set"),
723 "%s", _("Please set a username or disable the "
724 "audioscrobbler plugin, and restart VLC.\n"
725 "Visit http://www.last.fm/join/ to get an account."));
729 msg_Dbg(p_intf, "Handshake successful :)");
735 case VLC_AUDIOSCROBBLER_EFATAL:
736 msg_Warn(p_intf, "Exiting...");
741 /* protocol error : we'll try later */
742 HandleInterval(&next_exchange, &i_interval);
745 /* if handshake failed let's restart the loop */
750 msg_Dbg(p_intf, "Going to submit some data...");
752 if (asprintf(&psz_submit, "s=%s", p_sys->psz_auth_token) == -1)
755 /* forge the HTTP POST request */
756 vlc_mutex_lock(&p_sys->lock);
757 audioscrobbler_song_t *p_song;
758 for (int i_song = 0 ; i_song < p_sys->i_songs ; i_song++)
760 char *psz_submit_song, *psz_submit_tmp;
761 p_song = &p_sys->p_queue[i_song];
762 if (asprintf(&psz_submit_song,
772 i_song, p_song->psz_a,
773 i_song, p_song->psz_t,
774 i_song, (unsigned)p_song->date, /* HACK: %ju (uintmax_t) unsupported on Windows */
778 i_song, p_song->psz_b,
779 i_song, p_song->psz_n,
780 i_song, p_song->psz_m
782 { /* Out of memory */
783 vlc_mutex_unlock(&p_sys->lock);
786 psz_submit_tmp = psz_submit;
787 if (asprintf(&psz_submit, "%s%s",
788 psz_submit_tmp, psz_submit_song) == -1)
789 { /* Out of memory */
790 free(psz_submit_tmp);
791 free(psz_submit_song);
792 vlc_mutex_unlock(&p_sys->lock);
795 free(psz_submit_song);
796 free(psz_submit_tmp);
798 vlc_mutex_unlock(&p_sys->lock);
800 int i_post_socket = net_ConnectTCP(p_intf, p_sys->p_submit_url.psz_host,
801 p_sys->p_submit_url.i_port);
803 if (i_post_socket == -1)
805 /* If connection fails, we assume we must handshake again */
806 HandleInterval(&next_exchange, &i_interval);
807 b_handshaked = false;
812 /* we transmit the data */
813 int i_net_ret = net_Printf(p_intf, i_post_socket,
815 "Accept-Encoding: identity\n"
816 "Content-length: %zu\n"
817 "Connection: close\n"
818 "Content-type: application/x-www-form-urlencoded\n"
820 "User-agent: VLC media player/"VERSION"\r\n"
824 p_sys->p_submit_url.psz_path, strlen(psz_submit),
825 p_sys->p_submit_url.psz_host, psz_submit
831 /* If connection fails, we assume we must handshake again */
832 HandleInterval(&next_exchange, &i_interval);
833 b_handshaked = false;
837 /* FIXME: this might wait forever */
838 struct pollfd ufd = { .fd = i_post_socket, .events = POLLIN };
839 while( poll( &ufd, 1, -1 ) == -1 );
841 /* FIXME: With TCP, you should never assume that a single read will
842 * return the entire response... */
843 i_net_ret = recv(i_post_socket, p_buffer, sizeof(p_buffer) - 1, 0);
846 /* if we get no answer, something went wrong : try again */
850 net_Close(i_post_socket);
851 p_buffer[i_net_ret] = '\0';
853 char *failed = strstr((char *) p_buffer, "FAILED");
856 msg_Warn(p_intf, "%s", failed);
857 HandleInterval(&next_exchange, &i_interval);
861 if (strstr((char *) p_buffer, "BADSESSION"))
863 msg_Err(p_intf, "Authentication failed (BADSESSION), are you connected to last.fm with another program ?");
864 b_handshaked = false;
865 HandleInterval(&next_exchange, &i_interval);
869 if (strstr((char *) p_buffer, "OK"))
871 for (int i = 0; i < p_sys->i_songs; i++)
872 DeleteSong(&p_sys->p_queue[i]);
876 msg_Dbg(p_intf, "Submission successful!");
880 msg_Err(p_intf, "Authentication failed, handshaking again (%s)",
882 b_handshaked = false;
883 HandleInterval(&next_exchange, &i_interval);
887 vlc_restorecancel(canc);