<div dir="ltr">I have made the requested changes.</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Apr 20, 2020 at 9:35 PM Kartik Ohri <<a href="mailto:kartikohri13@gmail.com">kartikohri13@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">VLC already has the audioscrobbler module to submit scrobbles to<br>
<a href="http://last.fm" rel="noreferrer" target="_blank">last.fm</a> and other services with a similar API. This module extends<br>
that functionality to allow submission of listens to ListenBrainz.<br>
The existing audioscrobbler module is incompatible with ListenBrainz<br>
due to difference in authentication procedures and REST API for<br>
submissions.<br>
<br>
The term scrobble is a trademarked term by Last.fm, therefore the<br>
term listen used instead. More information about ListenBrainz is<br>
available at listenbrainz [dot] org.<br>
---<br>
modules/misc/Makefile.am | 4 +<br>
modules/misc/listenbrainz.c | 607 ++++++++++++++++++++++++++++++++++++<br>
2 files changed, 611 insertions(+)<br>
create mode 100755 modules/misc/listenbrainz.c<br>
<br>
diff --git a/modules/misc/Makefile.am b/modules/misc/Makefile.am<br>
index 78f9b09710..ed3ef24ee6 100644<br>
--- a/modules/misc/Makefile.am<br>
+++ b/modules/misc/Makefile.am<br>
@@ -8,6 +8,10 @@ libaudioscrobbler_plugin_la_SOURCES = misc/audioscrobbler.c<br>
libaudioscrobbler_plugin_la_LIBADD = $(SOCKET_LIBS)<br>
misc_LTLIBRARIES += <a href="http://libaudioscrobbler_plugin.la" rel="noreferrer" target="_blank">libaudioscrobbler_plugin.la</a><br>
<br>
+liblistenbrainz_plugin_la_SOURCES = misc/listenbrainz.c<br>
+liblistenbrainz_plugin_la_LIBADD = $(SOCKET_LIBS)<br>
+misc_LTLIBRARIES += <a href="http://liblistenbrainz_plugin.la" rel="noreferrer" target="_blank">liblistenbrainz_plugin.la</a><br>
+<br>
libexport_plugin_la_SOURCES = \<br>
misc/playlist/html.c \<br>
misc/playlist/m3u.c \<br>
diff --git a/modules/misc/listenbrainz.c b/modules/misc/listenbrainz.c<br>
new file mode 100755<br>
index 0000000000..ae251b53e6<br>
--- /dev/null<br>
+++ b/modules/misc/listenbrainz.c<br>
@@ -0,0 +1,607 @@<br>
+/*****************************************************************************<br>
+ * listenbrainz.c : ListenBrainz submission plugin<br>
+ * ListenBrainz Submit Listens API 1<br>
+ * <a href="https://api.listenbrainz.org/1/submit-listens" rel="noreferrer" target="_blank">https://api.listenbrainz.org/1/submit-listens</a><br>
+ *****************************************************************************<br>
+ * Copyright (C) 2020 VLC authors and VideoLAN<br>
+ *<br>
+ * Author: Kartik Ohri <kartikohri13 at gmail dot com><br>
+ *<br>
+ * This program is free software; you can redistribute it and/or modify it<br>
+ * under the terms of the GNU Lesser General Public License as published by<br>
+ * the Free Software Foundation; either version 2.1 of the License, or<br>
+ * (at your option) any later version.<br>
+ *<br>
+ * This program is distributed in the hope that it will be useful,<br>
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br>
+ * GNU Lesser General Public License for more details.<br>
+ *<br>
+ * You should have received a copy of the GNU Lesser General Public License<br>
+ * along with this program; if not, write to the Free Software Foundation,<br>
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.<br>
+ *****************************************************************************/<br>
+<br>
+#ifdef HAVE_CONFIG_H<br>
+#include "config.h"<br>
+#endif<br>
+<br>
+#include <assert.h><br>
+#include <time.h><br>
+<br>
+#include <vlc_common.h><br>
+#include <vlc_plugin.h><br>
+#include <vlc_interface.h><br>
+#include <vlc_input_item.h><br>
+#include <vlc_dialog.h><br>
+#include <vlc_meta.h><br>
+#include <vlc_memstream.h><br>
+#include <vlc_stream.h><br>
+#include <vlc_url.h><br>
+#include <vlc_tls.h><br>
+#include <vlc_player.h><br>
+#include <vlc_playlist.h><br>
+#include <vlc_vector.h><br>
+#include <vlc_interrupt.h><br>
+<br>
+typedef struct listen_t {<br>
+ char *psz_artist;<br>
+ char *psz_title;<br>
+ char *psz_album;<br>
+ char *psz_track_number;<br>
+ char *psz_musicbrainz_recording_id;<br>
+ int i_length;<br>
+ time_t date;<br>
+} listen_t;<br>
+<br>
+typedef struct VLC_VECTOR (listen_t) vlc_vector_listen_t;<br>
+<br>
+struct intf_sys_t {<br>
+ vlc_vector_listen_t queue;<br>
+<br>
+ vlc_player_t *player;<br>
+ struct vlc_player_listener_id *player_listener;<br>
+ struct vlc_player_timer_id *timer_listener;<br>
+<br>
+ vlc_mutex_t lock;<br>
+ vlc_cond_t wait; // song to submit event<br>
+ vlc_thread_t thread; // thread to submit song<br>
+ vlc_interrupt_t *interrupt;<br>
+ bool live;<br>
+<br>
+ vlc_url_t p_submit_url; // where to submit data<br>
+ char *psz_user_token; // authentication token<br>
+<br>
+ listen_t p_current_song;<br>
+ bool b_meta_read; // check if song metadata is already read<br>
+ vlc_tick_t time_played;<br>
+};<br>
+<br>
+static int Open (vlc_object_t *);<br>
+<br>
+static void Close (vlc_object_t *);<br>
+<br>
+static void *Run (void *);<br>
+<br>
+#define USER_TOKEN_TEXT N_("User token")<br>
+#define USER_TOKEN_LONGTEXT N_("The user token of your ListenBrainz account")<br>
+#define URL_TEXT N_("Submission URL")<br>
+#define URL_LONGTEXT N_("The URL set for an alternative ListenBrainz instance")<br>
+<br>
+vlc_module_begin ()<br>
+ set_category(CAT_INTERFACE)<br>
+ set_subcategory(SUBCAT_INTERFACE_CONTROL)<br>
+ set_shortname (N_ ("ListenBrainz"))<br>
+ set_description (N_ ("Submit listens to ListenBrainz"))<br>
+ add_string("listenbrainz-user-token" , "" , USER_TOKEN_TEXT , USER_TOKEN_LONGTEXT , false)<br>
+ add_string("listenbrainz-submission-url" , "<a href="http://api.listenbrainz.org" rel="noreferrer" target="_blank">api.listenbrainz.org</a>" , URL_TEXT , URL_LONGTEXT , false)<br>
+ set_capability("interface" , 0)<br>
+ set_callbacks(Open , Close)<br>
+vlc_module_end ()<br>
+<br>
+static void DeleteSong (listen_t *p_song)<br>
+{<br>
+ FREENULL (p_song->psz_artist);<br>
+ FREENULL (p_song->psz_album);<br>
+ FREENULL (p_song->psz_title);<br>
+ FREENULL (p_song->psz_musicbrainz_recording_id);<br>
+ FREENULL (p_song->psz_track_number);<br>
+ p_song->i_length = 0;<br>
+ p_song->date = 0;<br>
+}<br>
+<br>
+static void DeleteSongQueue(intf_thread_t *p_this)<br>
+{<br>
+ intf_sys_t *p_sys = p_this->p_sys;<br>
+<br>
+ for (size_t i = 0; i < p_sys->queue.size; ++i)<br>
+ DeleteSong(&p_sys->queue.data[i]);<br>
+ vlc_vector_clear (&p_sys->queue);<br>
+}<br>
+<br>
+static void ReadMetaData (intf_thread_t *p_this , input_item_t *item)<br>
+{<br>
+ intf_sys_t *p_sys = p_this->p_sys;<br>
+<br>
+ if (item == NULL)<br>
+ return;<br>
+<br>
+ vlc_mutex_lock (&p_sys->lock);<br>
+<br>
+ p_sys->b_meta_read = true;<br>
+ time (&p_sys->p_current_song.date);<br>
+<br>
+/* The retrieved metadata is encoded and then decoded to avoid UTF errors<br>
+ * while sending the JSON payload. Encoding the metadata prevents all the<br>
+ * errors. But the encoded characters are not decoded automatically by the<br>
+ * server and processed as such. Therefore, vlc_decode_uri is called on<br>
+ * the output of vlc_encode_uri. */<br>
+#define RETRIEVE_METADATA(a , b) do { \<br>
+ char *psz_data = input_item_Get##b(item); \<br>
+ if (psz_data && *psz_data) { \<br>
+ free(a); \<br>
+ char *psz_encoded = vlc_uri_encode(psz_data); \<br>
+ if (psz_encoded && *psz_encoded) \<br>
+ a = vlc_uri_decode(psz_encoded); \<br>
+ else \<br>
+ a = 0; \<br>
+ } \<br>
+ free(psz_data); \<br>
+ } while (0)<br>
+<br>
+ RETRIEVE_METADATA(p_sys->p_current_song.psz_artist , AlbumArtist);<br>
+ if (!p_sys->p_current_song.psz_artist)<br>
+ {<br>
+ RETRIEVE_METADATA(p_sys->p_current_song.psz_artist , Artist);<br>
+ if (!p_sys->p_current_song.psz_artist)<br>
+ {<br>
+ DeleteSong (&p_sys->p_current_song);<br>
+ goto error;<br>
+ }<br>
+ }<br>
+<br>
+ RETRIEVE_METADATA(p_sys->p_current_song.psz_title , Title);<br>
+ if (!p_sys->p_current_song.psz_title)<br>
+ {<br>
+ DeleteSong (&p_sys->p_current_song);<br>
+ goto error;<br>
+ }<br>
+<br>
+ RETRIEVE_METADATA(p_sys->p_current_song.psz_album , Album);<br>
+ RETRIEVE_METADATA(p_sys->p_current_song.psz_musicbrainz_recording_id , TrackID);<br>
+ RETRIEVE_METADATA(p_sys->p_current_song.psz_track_number , TrackNum);<br>
+ p_sys->p_current_song.i_length = SEC_FROM_VLC_TICK (input_item_GetDuration (item));<br>
+ msg_Dbg (p_this , "Meta data registered");<br>
+ vlc_cond_signal (&p_sys->wait);<br>
+<br>
+error:<br>
+ vlc_mutex_unlock (&p_sys->lock);<br>
+<br>
+#undef RETRIEVE_METADATA<br>
+}<br>
+<br>
+static void Enqueue (intf_thread_t *p_this)<br>
+{<br>
+ intf_sys_t *p_sys = p_this->p_sys;<br>
+<br>
+ p_sys->b_meta_read = false;<br>
+ /* Song not yet initialized */<br>
+ if(p_sys->p_current_song.date == 0)<br>
+ return;<br>
+ vlc_mutex_lock (&p_sys->lock);<br>
+<br>
+ if (EMPTY_STR(p_sys->p_current_song.psz_artist) ||<br>
+ EMPTY_STR(p_sys->p_current_song.psz_title))<br>
+ {<br>
+ msg_Dbg (p_this , "Missing artist or title, not submitting");<br>
+ goto error;<br>
+ }<br>
+<br>
+ if (p_sys->p_current_song.i_length == 0)<br>
+ p_sys->p_current_song.i_length = p_sys->time_played;<br>
+<br>
+ if (p_sys->time_played < 30)<br>
+ {<br>
+ msg_Dbg (p_this , "Song not listened long enough, not submitting");<br>
+ goto error;<br>
+ }<br>
+<br>
+ msg_Dbg (p_this , "Song will be submitted.");<br>
+ /* Transfer the ownership of allocated datas to the queue */<br>
+ vlc_vector_push (&p_sys->queue , p_sys->p_current_song);<br>
+ memset(&p_sys->p_current_song, 0, sizeof(p_sys->p_current_song));<br>
+<br>
+ vlc_cond_signal (&p_sys->wait);<br>
+ vlc_mutex_unlock (&p_sys->lock);<br>
+ return;<br>
+<br>
+error:<br>
+ DeleteSong (&p_sys->p_current_song);<br>
+ vlc_mutex_unlock (&p_sys->lock);<br>
+}<br>
+<br>
+static void PlayerStateChanged (vlc_player_t *player , enum vlc_player_state state , void *data)<br>
+{<br>
+ intf_thread_t *intf = data;<br>
+ intf_sys_t *p_sys = intf->p_sys;<br>
+<br>
+ if (vlc_player_GetVideoTrackCount (player))<br>
+ return;<br>
+<br>
+ if (!p_sys->b_meta_read && state >= VLC_PLAYER_STATE_PLAYING)<br>
+ {<br>
+ input_item_t *item = vlc_player_GetCurrentMedia (p_sys->player);<br>
+ ReadMetaData (intf , item);<br>
+ return;<br>
+ }<br>
+<br>
+ if (state == VLC_PLAYER_STATE_STOPPED)<br>
+ Enqueue (intf);<br>
+}<br>
+<br>
+static void OnTimerUpdate (const struct vlc_player_timer_point *value , void *data)<br>
+{<br>
+ intf_thread_t *intf = data;<br>
+ intf_sys_t *p_sys = intf->p_sys;<br>
+ p_sys->time_played = SEC_FROM_VLC_TICK (value->ts - VLC_TICK_0);<br>
+}<br>
+<br>
+static void OnTimerStopped (vlc_tick_t system_date , void *data)<br>
+{<br>
+ (void) system_date;<br>
+ (void) data;<br>
+}<br>
+<br>
+static void OnCurrentMediaChanged (vlc_player_t *player , input_item_t *new_media , void *data)<br>
+{<br>
+ intf_thread_t *intf = data;<br>
+ Enqueue (intf);<br>
+<br>
+ intf_sys_t *p_sys = intf->p_sys;<br>
+ p_sys->b_meta_read = false;<br>
+<br>
+ if (!new_media || vlc_player_GetVideoTrackCount (player))<br>
+ return;<br>
+<br>
+ p_sys->time_played = 0;<br>
+ if (input_item_IsPreparsed (new_media))<br>
+ ReadMetaData (intf , new_media);<br>
+}<br>
+<br>
+static void OnMetadataChanged (vlc_player_t *player, input_item_t *media, void *data)<br>
+{<br>
+ intf_thread_t *intf = data;<br>
+ Enqueue (intf);<br>
+<br>
+ intf_sys_t *p_sys = intf->p_sys;<br>
+ p_sys->b_meta_read = false;<br>
+<br>
+ if (vlc_player_GetVideoTrackCount (player))<br>
+ return;<br>
+<br>
+ p_sys->time_played = 0;<br>
+ if (input_item_IsPreparsed (media))<br>
+ ReadMetaData (intf , media);<br>
+}<br>
+<br>
+static char *PreparePayload (intf_thread_t *p_this)<br>
+{<br>
+ intf_sys_t *p_sys = p_this->p_sys;<br>
+ struct vlc_memstream payload;<br>
+ vlc_memstream_open (&payload);<br>
+<br>
+ if (p_sys->queue.size == 1)<br>
+ vlc_memstream_printf (&payload , "{\"listen_type\":\"single\",\"payload\":[");<br>
+ else<br>
+ vlc_memstream_printf (&payload , "{\"listen_type\":\"import\",\"payload\":[");<br>
+<br>
+ for (int i_song = 0 ; i_song < ( int ) p_sys->queue.size ; i_song++)<br>
+ {<br>
+ listen_t *p_song = &p_sys->queue.data[ i_song ];<br>
+<br>
+ vlc_memstream_printf (&payload , "{\"listened_at\": %"PRIu64 , ( uint64_t ) p_song->date);<br>
+ vlc_memstream_printf (&payload , ", \"track_metadata\": {\"artist_name\": \"%s\", " ,<br>
+ p_song->psz_artist);<br>
+ vlc_memstream_printf (&payload , " \"track_name\": \"%s\", " , p_song->psz_title);<br>
+ if (!EMPTY_STR (p_song->psz_album))<br>
+ vlc_memstream_printf (&payload , " \"release_name\": \"%s\"" , p_song->psz_album);<br>
+ if (!EMPTY_STR (p_song->psz_musicbrainz_recording_id))<br>
+ vlc_memstream_printf (&payload , ", \"additional_info\": {\"recording_mbid\":\"%s\"} " ,<br>
+ p_song->psz_musicbrainz_recording_id);<br>
+ vlc_memstream_printf (&payload , "}}");<br>
+ }<br>
+<br>
+ vlc_memstream_printf (&payload , "]}");<br>
+<br>
+ int i_status = vlc_memstream_close (&payload);<br>
+ if (!i_status)<br>
+ {<br>
+ msg_Dbg (p_this , "Payload: %s" , payload.ptr);<br>
+ return payload.ptr;<br>
+ }<br>
+ else<br>
+ return NULL;<br>
+}<br>
+<br>
+static char *PrepareRequest (intf_thread_t *p_this , char *payload)<br>
+{<br>
+ intf_sys_t *p_sys = p_this->p_sys;<br>
+ struct vlc_memstream request;<br>
+<br>
+ vlc_memstream_open (&request);<br>
+ vlc_memstream_printf (&request , "POST %s HTTP/1.1\r\n" , p_sys->p_submit_url.psz_path);<br>
+ vlc_memstream_printf (&request , "Host: %s\r\n" , p_sys->p_submit_url.psz_host);<br>
+ vlc_memstream_printf (&request , "Authorization: Token %s\r\n" , p_sys->psz_user_token);<br>
+ vlc_memstream_puts (&request , "User-Agent: "PACKAGE"/"VERSION"\r\n");<br>
+ vlc_memstream_puts (&request , "Connection: close\r\n");<br>
+ vlc_memstream_puts (&request , "Accept-Encoding: identity\r\n");<br>
+ vlc_memstream_printf (&request , "Content-Length: %zu\r\n" , strlen (payload));<br>
+ vlc_memstream_puts (&request , "\r\n");<br>
+ vlc_memstream_puts (&request , payload);<br>
+ vlc_memstream_puts (&request , "\r\n\r\n");<br>
+<br>
+ free (payload);<br>
+<br>
+ int i_status = vlc_memstream_close (&request);<br>
+ if (!i_status)<br>
+ return request.ptr;<br>
+ else<br>
+ return NULL;<br>
+}<br>
+<br>
+static int SendRequest (intf_thread_t *p_this , char *request)<br>
+{<br>
+ uint8_t p_buffer[1024];<br>
+ int i_ret;<br>
+<br>
+ intf_sys_t *p_sys = p_this->p_sys;<br>
+ vlc_tls_client_t *creds = vlc_tls_ClientCreate (VLC_OBJECT (p_this));<br>
+ vlc_tls_t *sock = vlc_tls_SocketOpenTLS (creds , p_sys->p_submit_url.psz_host , 443 , NULL , NULL , NULL);<br>
+<br>
+ if (sock == NULL)<br>
+ {<br>
+ vlc_tls_ClientDelete(creds);<br>
+ return VLC_EGENERIC;<br>
+ }<br>
+<br>
+ i_ret = vlc_tls_Write (sock , request , strlen (request));<br>
+<br>
+ if (i_ret == -1)<br>
+ {<br>
+ vlc_tls_Close (sock);<br>
+ vlc_tls_ClientDelete(creds);<br>
+ return VLC_EGENERIC;<br>
+ }<br>
+<br>
+ i_ret = vlc_tls_Read (sock , p_buffer , sizeof (p_buffer) - 1 , false);<br>
+ msg_Dbg (p_this , "Response: %s" , ( char * ) p_buffer);<br>
+ vlc_tls_Close (sock);<br>
+ vlc_tls_ClientDelete(creds);<br>
+ if (i_ret <= 0)<br>
+ {<br>
+ msg_Warn (p_this , "No response");<br>
+ return VLC_EGENERIC;<br>
+ }<br>
+ p_buffer[ i_ret ] = '\0';<br>
+<br>
+ if (strstr (( char * ) p_buffer , "HTTP/1") == ( char * ) p_buffer)<br>
+ {<br>
+ char *status = strchr((char *)p_buffer, '\n');<br>
+ if(status)<br>
+ *status = 0;<br>
+<br>
+ if (strstr (( char * ) p_buffer, "200"))<br>
+ {<br>
+ msg_Dbg (p_this, "Submission successful!");<br>
+ return VLC_SUCCESS;<br>
+ }<br>
+ else if (strstr (( char * ) p_buffer, "401"))<br>
+ msg_Warn (p_this, "Authentication Error");<br>
+ else<br>
+ msg_Warn (p_this, "Invalid Request");<br>
+ }<br>
+ else<br>
+ msg_Warn (p_this, "Invalid response");<br>
+<br>
+ return VLC_EGENERIC;<br>
+}<br>
+<br>
+static int Configure (intf_thread_t *p_intf)<br>
+{<br>
+ int i_ret;<br>
+ char *psz_submission_url , *psz_url;<br>
+ intf_sys_t *p_sys = p_intf->p_sys;<br>
+<br>
+ p_sys->psz_user_token = var_InheritString (p_intf , "listenbrainz-user-token");<br>
+ if (EMPTY_STR (p_sys->psz_user_token))<br>
+ {<br>
+ vlc_dialog_display_error (p_intf ,<br>
+ _ ("ListenBrainz User Token not set") , "%s" ,<br>
+ _ ("Please set a user token or disable the ListenBrainz plugin, and restart VLC.\n"<br>
+ " Visit <a href="https://listenbrainz.org/profile/" rel="noreferrer" target="_blank">https://listenbrainz.org/profile/</a> to get a user token."));<br>
+ return VLC_EGENERIC;<br>
+ }<br>
+<br>
+ psz_submission_url = var_InheritString (p_intf , "listenbrainz-submission-url");<br>
+ if (psz_submission_url)<br>
+ {<br>
+ i_ret = asprintf (&psz_url , "https://%s/1/submit-listens" , psz_submission_url);<br>
+ free (psz_submission_url);<br>
+ if (i_ret != -1)<br>
+ {<br>
+ vlc_UrlParse (&p_sys->p_submit_url , psz_url);<br>
+ free (psz_url);<br>
+ return VLC_SUCCESS;<br>
+ }<br>
+ }<br>
+<br>
+ vlc_dialog_display_error (p_intf ,<br>
+ _ ("ListenBrainz API URL Invalid") , "%s" ,<br>
+ _ ("Please set a valid endpoint URL. The default value is <a href="http://api.listenbrainz.org" rel="noreferrer" target="_blank">api.listenbrainz.org</a> ."));<br>
+ return VLC_EGENERIC;<br>
+}<br>
+<br>
+static int Open (vlc_object_t *p_this)<br>
+{<br>
+ intf_thread_t *p_intf = ( intf_thread_t * ) p_this;<br>
+ intf_sys_t *p_sys = calloc (1 , sizeof (intf_sys_t));<br>
+<br>
+ if (!p_sys)<br>
+ return VLC_ENOMEM;<br>
+<br>
+ p_intf->p_sys = p_sys;<br>
+ p_sys->live = true;<br>
+<br>
+ if (Configure (p_intf) != VLC_SUCCESS)<br>
+ goto error;<br>
+<br>
+ static struct vlc_player_cbs const player_cbs =<br>
+ {<br>
+ .on_state_changed = PlayerStateChanged ,<br>
+ .on_current_media_changed = OnCurrentMediaChanged ,<br>
+ .on_media_meta_changed = OnMetadataChanged ,<br>
+ };<br>
+ static struct vlc_player_timer_cbs const timer_cbs =<br>
+ {<br>
+ .on_update = OnTimerUpdate ,<br>
+ .on_discontinuity = OnTimerStopped ,<br>
+ };<br>
+<br>
+ vlc_playlist_t *playlist = vlc_intf_GetMainPlaylist (p_intf);<br>
+ p_sys->player = vlc_playlist_GetPlayer (playlist);<br>
+<br>
+ vlc_player_Lock (p_sys->player);<br>
+ p_sys->player_listener = vlc_player_AddListener (p_sys->player , &player_cbs , p_intf);<br>
+ vlc_player_Unlock (p_sys->player);<br>
+<br>
+ if (!p_sys->player_listener)<br>
+ goto error;<br>
+<br>
+ p_sys->timer_listener = vlc_player_AddTimer (p_sys->player , VLC_TICK_FROM_SEC (1) , &timer_cbs , p_intf);<br>
+ if (!p_sys->timer_listener)<br>
+ goto error;<br>
+<br>
+ p_sys->interrupt = vlc_interrupt_create();<br>
+ if (unlikely(p_sys->interrupt == NULL))<br>
+ goto error;<br>
+<br>
+ vlc_mutex_init (&p_sys->lock);<br>
+ vlc_cond_init (&p_sys->wait);<br>
+<br>
+ if (vlc_clone (&p_sys->thread , Run , p_intf , VLC_THREAD_PRIORITY_LOW))<br>
+ {<br>
+ vlc_interrupt_destroy(p_sys->interrupt);<br>
+ goto error;<br>
+ }<br>
+<br>
+ vlc_vector_init(&p_sys->queue);<br>
+ return VLC_SUCCESS;<br>
+<br>
+error:<br>
+ if (p_sys->player_listener)<br>
+ {<br>
+ vlc_player_Lock (p_sys->player);<br>
+ vlc_player_RemoveListener (p_sys->player , p_sys->player_listener);<br>
+ vlc_player_Unlock (p_sys->player);<br>
+ }<br>
+ if (p_sys->timer_listener)<br>
+ vlc_player_RemoveTimer (p_sys->player , p_sys->timer_listener);<br>
+ vlc_UrlClean (&p_sys->p_submit_url);<br>
+ free(p_sys->psz_user_token);<br>
+ free (p_sys);<br>
+ return VLC_EGENERIC;<br>
+}<br>
+<br>
+static void Close (vlc_object_t *p_this)<br>
+{<br>
+ intf_thread_t *p_intf = ( intf_thread_t * ) p_this;<br>
+ intf_sys_t *p_sys = p_intf->p_sys;<br>
+<br>
+ vlc_mutex_lock(&p_sys->lock);<br>
+ p_sys->live = false;<br>
+ vlc_cond_signal (&p_sys->wait);<br>
+ vlc_mutex_unlock(&p_sys->lock);<br>
+<br>
+ vlc_interrupt_kill(p_sys->interrupt);<br>
+ vlc_join (p_sys->thread , NULL);<br>
+ vlc_interrupt_destroy(p_sys->interrupt);<br>
+<br>
+ vlc_player_Lock (p_sys->player);<br>
+ vlc_player_RemoveListener (p_sys->player , p_sys->player_listener);<br>
+ vlc_player_Unlock (p_sys->player);<br>
+<br>
+ vlc_player_RemoveTimer (p_sys->player , p_sys->timer_listener);<br>
+<br>
+ DeleteSongQueue(p_intf);<br>
+ DeleteSong(&p_sys->p_current_song);<br>
+<br>
+ vlc_UrlClean (&p_sys->p_submit_url);<br>
+ free(p_sys->psz_user_token);<br>
+<br>
+ free (p_sys);<br>
+}<br>
+<br>
+static void *Run (void *data)<br>
+{<br>
+ intf_thread_t *p_intf = data;<br>
+ bool b_wait = 0;<br>
+ char *request , *payload;<br>
+<br>
+ intf_sys_t *p_sys = p_intf->p_sys;<br>
+<br>
+ vlc_interrupt_set(p_sys->interrupt);<br>
+<br>
+ vlc_mutex_lock (&p_sys->lock);<br>
+ for (;;)<br>
+ {<br>
+ if (b_wait)<br>
+ {<br>
+ vlc_tick_t deadline = vlc_tick_now() + VLC_TICK_FROM_SEC (60);<br>
+ int ret = 0;<br>
+ while (p_sys->live && ret == 0) // wait for 1 min<br>
+ ret = vlc_cond_timedwait (&p_sys->wait , &p_sys->lock, deadline);<br>
+ }<br>
+<br>
+ while (p_sys->live && p_sys->queue.size == 0)<br>
+ vlc_cond_wait (&p_sys->wait , &p_sys->lock);<br>
+<br>
+ if (!p_sys->live)<br>
+ break;<br>
+<br>
+ payload = PreparePayload (p_intf);<br>
+ vlc_mutex_unlock (&p_sys->lock);<br>
+<br>
+ if (!payload)<br>
+ {<br>
+ msg_Warn (p_intf , "Error: Unable to generate payload");<br>
+ return NULL;<br>
+ }<br>
+<br>
+ request = PrepareRequest (p_intf , payload);<br>
+ if (!request)<br>
+ {<br>
+ msg_Warn (p_intf , "Error: Unable to generate request body");<br>
+ return NULL;<br>
+ }<br>
+<br>
+ int ret = SendRequest (p_intf , request);<br>
+ free(request);<br>
+<br>
+ vlc_mutex_lock (&p_sys->lock);<br>
+<br>
+ if (ret == VLC_SUCCESS)<br>
+ {<br>
+ DeleteSongQueue(p_intf);<br>
+ b_wait = false;<br>
+ }<br>
+ else<br>
+ {<br>
+ msg_Warn (p_intf , "Error: Could not transmit request");<br>
+ b_wait = true;<br>
+ }<br>
+ }<br>
+<br>
+ vlc_mutex_unlock (&p_sys->lock);<br>
+ return NULL;<br>
+}<br>
+<br>
-- <br>
2.20.1<br>
<br>
</blockquote></div>