<div dir="ltr">Please ignore this email and see the one sent as reply to the correct thread.</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sun, Mar 8, 2020 at 10:41 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">From: "Kartik Ohri <a href="mailto:kartikohri13@gmail.com" target="_blank">kartikohri13@gmail.com</a>" <<a href="mailto:kartikohri13@gmail.com" target="_blank">kartikohri13@gmail.com</a>><br>
<br>
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 | 583 ++++++++++++++++++++++++++++++++++++<br>
 2 files changed, 587 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..d75b87ba2e<br>
--- /dev/null<br>
+++ b/modules/misc/listenbrainz.c<br>
@@ -0,0 +1,583 @@<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>
+ * Author: Kartik Ohri <kartikohri13 at gmail dot com><br>
+ *<br>
+ * Copyright (C) 2020 VLC authors and VideoLAN<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>
+<br>
+typedef struct listen_t<br>
+{<br>
+    char *psz_artist;<br>
+    char *psz_title;<br>
+    char *psz_album;<br>
+    char *psz_track_number;<br>
+    int i_length;<br>
+    char *psz_musicbrainz_id;<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>
+{<br>
+    vlc_vector_listen_t p_queue;<br>
+<br>
+    vlc_playlist_t *playlist;<br>
+    struct vlc_playlist_listener_id *playlist_listener;<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>
+<br>
+    struct vlc_memstream payload, request;<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>
+<br>
+    int64_t i_played_time;<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>
+/****************************************************************************<br>
+ * Module descriptor<br>
+ ****************************************************************************/<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_id);<br>
+    FREENULL (p_song->psz_track_number);<br>
+    p_song->date = 0;<br>
+}<br>
+<br>
+static void ReadMetaData (intf_thread_t *p_this)<br>
+{<br>
+    bool b_skip = 0;<br>
+    intf_sys_t *p_sys = p_this->p_sys;<br>
+<br>
+    vlc_player_t *player = vlc_playlist_GetPlayer (p_sys->playlist);<br>
+    input_item_t *item = vlc_player_GetCurrentMedia (player);<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>
+#define RETRIEVE_METADATA(a, b) do { \<br>
+        char *psz_data = input_item_Get##b(item); \<br>
+        if (psz_data && *psz_data) \<br>
+            a = vlc_uri_encode(psz_data); \<br>
+        free(psz_data); \<br>
+    } while (0)<br>
+<br>
+    RETRIEVE_METADATA(p_sys->p_current_song.psz_artist, Artist);<br>
+    if ( !p_sys->p_current_song.psz_artist )<br>
+    {<br>
+        msg_Dbg (p_this, "Artist missing.");<br>
+        DeleteSong (&p_sys->p_current_song);<br>
+        b_skip = 1;<br>
+    }<br>
+<br>
+    RETRIEVE_METADATA(p_sys->p_current_song.psz_title, Title);<br>
+    if ( b_skip || !p_sys->p_current_song.psz_title )<br>
+    {<br>
+        msg_Dbg (p_this, "Track name missing.");<br>
+        DeleteSong (&p_sys->p_current_song);<br>
+        b_skip = 1;<br>
+    }<br>
+<br>
+    if ( !b_skip )<br>
+    {<br>
+        RETRIEVE_METADATA(p_sys->p_current_song.psz_album, Album);<br>
+        RETRIEVE_METADATA(p_sys->p_current_song.psz_musicbrainz_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>
+    vlc_mutex_unlock (&p_sys->lock);<br>
+<br>
+#undef RETRIEVE_METADATA<br>
+}<br>
+<br>
+static listen_t CopySong (listen_t song)<br>
+{<br>
+    listen_t *copy = (listen_t *) malloc (sizeof (*copy));<br>
+    memset (copy, 0, sizeof (*copy));<br>
+    if ( song.psz_title )<br>
+        copy->psz_title = strdup (song.psz_title);<br>
+    if ( song.psz_artist )<br>
+        copy->psz_artist = strdup (song.psz_artist);<br>
+    if ( song.psz_album )<br>
+        copy->psz_album = strdup (song.psz_album);<br>
+    if ( song.psz_musicbrainz_id )<br>
+        copy->psz_musicbrainz_id = strdup (song.psz_musicbrainz_id);<br>
+    if ( song.psz_track_number )<br>
+        copy->psz_track_number = strdup (song.psz_track_number);<br>
+    copy->date = song.date;<br>
+    return *copy;<br>
+}<br>
+<br>
+static void Enqueue (intf_thread_t *p_this)<br>
+{<br>
+    bool b_skip = 0;<br>
+    intf_sys_t *p_sys = p_this->p_sys;<br>
+<br>
+    vlc_mutex_lock (&p_sys->lock);<br>
+<br>
+    if ( !p_sys->p_current_song.psz_artist || !*p_sys->p_current_song.psz_artist ||<br>
+         !p_sys->p_current_song.psz_title || !*p_sys->p_current_song.psz_title )<br>
+    {<br>
+        msg_Dbg (p_this, "Missing artist or title, not submitting");<br>
+        b_skip = 1;<br>
+    }<br>
+<br>
+    if ( p_sys->p_current_song.i_length == 0 )<br>
+        p_sys->p_current_song.i_length = p_sys->i_played_time;<br>
+<br>
+    if ( !b_skip && p_sys->i_played_time < 30 )<br>
+    {<br>
+        msg_Dbg (p_this, "Song not listened long enough, not submitting");<br>
+        b_skip = 1;<br>
+    }<br>
+<br>
+    if ( !b_skip )<br>
+    {<br>
+        msg_Dbg (p_this, "Song will be submitted.");<br>
+        listen_t p_copy_to_queue = CopySong (p_sys->p_current_song);<br>
+        bool b_success = vlc_vector_push (&p_sys->p_queue, p_copy_to_queue);<br>
+        if ( !b_success )<br>
+            msg_Warn (p_this, "Error: Unable to enqueue song");<br>
+    }<br>
+<br>
+    vlc_cond_signal (&p_sys->wait);<br>
+    DeleteSong (&p_sys->p_current_song);<br>
+    p_sys->b_meta_read = false;<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>
+    {<br>
+        msg_Dbg (intf, "Not an audio-only input, not submitting");<br>
+        return;<br>
+    }<br>
+<br>
+    if ( !p_sys->b_meta_read && state >= VLC_PLAYER_STATE_PLAYING )<br>
+    {<br>
+        ReadMetaData (intf);<br>
+        return;<br>
+    }<br>
+<br>
+    if ( state == VLC_PLAYER_STATE_STOPPED )<br>
+        Enqueue (intf);<br>
+}<br>
+<br>
+static void UpdateState (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->i_played_time = SEC_FROM_VLC_TICK (value->ts - VLC_TICK_0);<br>
+}<br>
+<br>
+static void PlayingStopped (vlc_tick_t system_date, void *data){}<br>
+<br>
+static void PlaylistItemChanged (vlc_playlist_t *playlist, ssize_t index, void *data)<br>
+{<br>
+    VLC_UNUSED (index);<br>
+<br>
+    intf_thread_t *intf = data;<br>
+    if ( index > 0 )<br>
+        Enqueue (intf);<br>
+<br>
+    intf_sys_t *p_sys = intf->p_sys;<br>
+    p_sys->b_meta_read = false;<br>
+<br>
+    vlc_player_t *player = vlc_playlist_GetPlayer (playlist);<br>
+    input_item_t *item = vlc_player_GetCurrentMedia (player);<br>
+<br>
+    if ( !item || vlc_player_GetVideoTrackCount (player) )<br>
+    {<br>
+        msg_Dbg (intf, "Invalid item or not an audio-only input.");<br>
+        return;<br>
+    }<br>
+<br>
+    p_sys->i_played_time = 0;<br>
+<br>
+    if ( input_item_IsPreparsed (item) )<br>
+        ReadMetaData (intf);<br>
+}<br>
+<br>
+static int 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>
+    vlc_mutex_lock (&p_sys->lock);<br>
+<br>
+    if ( p_sys->p_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->p_queue.size; i_song++ )<br>
+    {<br>
+        listen_t *p_song = &p_sys->p_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>
+                              vlc_uri_decode (p_song->psz_artist));<br>
+        vlc_memstream_printf (&payload, " \"track_name\": \"%s\", ", vlc_uri_decode (p_song->psz_title));<br>
+        if ( !EMPTY_STR (p_song->psz_album) )<br>
+            vlc_memstream_printf (&payload, " \"release_name\": \"%s\"", vlc_uri_decode (p_song->psz_album));<br>
+        if ( !EMPTY_STR (p_song->psz_musicbrainz_id) )<br>
+            vlc_memstream_printf (&payload, ", \"additional_info\": {\"recording_mbid\":\"%s\"} ",<br>
+                                  vlc_uri_decode (p_song->psz_musicbrainz_id));<br>
+        vlc_memstream_printf (&payload, "}}");<br>
+    }<br>
+<br>
+    vlc_memstream_printf (&payload, "]}");<br>
+    vlc_mutex_unlock (&p_sys->lock);<br>
+<br>
+    int i_status = vlc_memstream_close (&payload);<br>
+    if ( !i_status )<br>
+        p_sys->payload = payload;<br>
+    msg_Dbg (p_this, "Payload: %s", payload.ptr);<br>
+    return i_status;<br>
+}<br>
+<br>
+static int PrepareRequest (intf_thread_t *p_this)<br>
+{<br>
+    intf_sys_t *p_sys = p_this->p_sys;<br>
+    struct vlc_memstream request;<br>
+<br>
+    vlc_mutex_lock (&p_sys->lock);<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", p_sys->payload.length);<br>
+    vlc_memstream_puts (&request, "\r\n");<br>
+    vlc_memstream_write (&request, p_sys->payload.ptr, p_sys->payload.length);<br>
+    vlc_memstream_puts (&request, "\r\n\r\n");<br>
+<br>
+    free (p_sys->payload.ptr);<br>
+<br>
+    vlc_mutex_unlock (&p_sys->lock);<br>
+<br>
+    int i_status = vlc_memstream_close (&request);<br>
+    if ( !i_status )<br>
+        p_sys->request = request;<br>
+    return i_status;<br>
+}<br>
+<br>
+static int SendRequest (intf_thread_t *p_this)<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>
+        free (p_sys->request.ptr);<br>
+        return 1;<br>
+    }<br>
+<br>
+    i_ret = vlc_tls_Write (sock, p_sys->request.ptr, p_sys->request.length);<br>
+    free (p_sys->request.ptr);<br>
+<br>
+    if ( i_ret == -1 )<br>
+    {<br>
+        vlc_tls_Close (sock);<br>
+        return 1;<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>
+    if ( i_ret <= 0 )<br>
+    {<br>
+        msg_Warn (p_this, "No response");<br>
+        return 1;<br>
+    }<br>
+    p_buffer[i_ret] = '\0';<br>
+    if ( strstr ((char *) p_buffer, "OK") )<br>
+    {<br>
+        vlc_vector_clear (&p_sys->p_queue);<br>
+        msg_Dbg (p_this, "Submission successful!");<br>
+    }<br>
+    else<br>
+    {<br>
+        msg_Warn (p_this, "Error: %s", (char *) p_buffer);<br>
+        return 1;<br>
+    }<br>
+<br>
+    return 0;<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>
+    bool b_fail = 0;<br>
+    int i_ret;<br>
+    char *psz_submission_url, *psz_url;<br>
+<br>
+    if ( !p_sys )<br>
+        return VLC_ENOMEM;<br>
+<br>
+    p_intf->p_sys = 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>
+        free (p_sys->psz_user_token);<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>
+        free (p_sys);<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>
+            b_fail = 1;<br>
+        vlc_UrlParse (&p_sys->p_submit_url, psz_url);<br>
+        free (psz_url);<br>
+    }<br>
+    else<br>
+        b_fail = 1;<br>
+<br>
+    if ( b_fail )<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>
+        free (p_sys);<br>
+        return VLC_EGENERIC;<br>
+    }<br>
+<br>
+    static struct vlc_playlist_callbacks const playlist_cbs =<br>
+            {<br>
+                    .on_current_index_changed = PlaylistItemChanged,<br>
+            };<br>
+    static struct vlc_player_cbs const player_cbs =<br>
+            {<br>
+                    .on_state_changed = PlayerStateChanged,<br>
+            };<br>
+    static struct vlc_player_timer_cbs const timer_cbs =<br>
+            {<br>
+                    .on_update = UpdateState,<br>
+                    .on_discontinuity = PlayingStopped,<br>
+            };<br>
+<br>
+    vlc_playlist_t *playlist = p_sys->playlist = vlc_intf_GetMainPlaylist (p_intf);<br>
+    vlc_player_t *player = vlc_playlist_GetPlayer (playlist);<br>
+<br>
+    vlc_playlist_Lock (playlist);<br>
+    p_sys->playlist_listener = vlc_playlist_AddListener (playlist, &playlist_cbs, p_intf, false);<br>
+    if ( !p_sys->playlist_listener )<br>
+    {<br>
+        vlc_playlist_Unlock (playlist);<br>
+        b_fail = 1;<br>
+    }<br>
+    else<br>
+    {<br>
+        p_sys->player_listener = vlc_player_AddListener (player, &player_cbs, p_intf);<br>
+        vlc_playlist_Unlock (playlist);<br>
+        if ( !p_sys->player_listener )<br>
+            b_fail = 1;<br>
+        else<br>
+        {<br>
+            p_sys->timer_listener = vlc_player_AddTimer (player, VLC_TICK_FROM_SEC (1), &timer_cbs, p_intf);<br>
+            if ( !p_sys->timer_listener )<br>
+                b_fail = 1;<br>
+        }<br>
+    }<br>
+    if ( !b_fail )<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>
+            b_fail = 1;<br>
+    }<br>
+    if ( b_fail )<br>
+    {<br>
+        if ( p_sys->playlist_listener )<br>
+        {<br>
+            vlc_playlist_Lock (playlist);<br>
+            if ( p_sys->player_listener )<br>
+                vlc_player_RemoveListener (player, p_sys->player_listener);<br>
+            if ( p_sys->timer_listener )<br>
+                vlc_player_RemoveTimer (player, p_sys->timer_listener);<br>
+            vlc_playlist_RemoveListener (playlist, p_sys->playlist_listener);<br>
+            vlc_playlist_Unlock (playlist);<br>
+        }<br>
+        free (p_sys);<br>
+        return VLC_EGENERIC;<br>
+    }<br>
+<br>
+    return VLC_SUCCESS;<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>
+    vlc_playlist_t *playlist = p_sys->playlist;<br>
+<br>
+    vlc_cancel (p_sys->thread);<br>
+    vlc_join (p_sys->thread, NULL);<br>
+<br>
+    vlc_vector_clear (&p_sys->p_queue);<br>
+    vlc_UrlClean (&p_sys->p_submit_url);<br>
+<br>
+    vlc_playlist_Lock (playlist);<br>
+    vlc_player_RemoveListener (vlc_playlist_GetPlayer (playlist), p_sys->player_listener);<br>
+    vlc_playlist_RemoveListener (playlist, p_sys->playlist_listener);<br>
+    vlc_playlist_Unlock (playlist);<br>
+<br>
+    free (p_sys);<br>
+}<br>
+<br>
+static void *Run (void *data)<br>
+{<br>
+    intf_thread_t *p_intf = data;<br>
+    int canc = vlc_savecancel ();<br>
+    int i_status;<br>
+    bool b_wait = 0;<br>
+<br>
+    intf_sys_t *p_sys = p_intf->p_sys;<br>
+<br>
+    while ( 1 )<br>
+    {<br>
+        vlc_restorecancel (canc);<br>
+<br>
+        if ( b_wait )<br>
+            vlc_tick_wait (vlc_tick_now () + VLC_TICK_FROM_SEC (60)); // wait for 1 min<br>
+<br>
+        vlc_mutex_lock (&p_sys->lock);<br>
+        mutex_cleanup_push (&p_sys->lock) ;<br>
+<br>
+                while ( p_sys->p_queue.size == 0 )<br>
+                    vlc_cond_wait (&p_sys->wait, &p_sys->lock);<br>
+<br>
+        vlc_cleanup_pop ();<br>
+        vlc_mutex_unlock (&p_sys->lock);<br>
+        canc = vlc_savecancel ();<br>
+<br>
+        i_status = PreparePayload (p_intf);<br>
+        if ( i_status )<br>
+        {<br>
+            msg_Warn (p_intf, "Error: Unable to generate payload");<br>
+            break;<br>
+        }<br>
+<br>
+        i_status = PrepareRequest (p_intf);<br>
+        if ( i_status )<br>
+        {<br>
+            msg_Warn (p_intf, "Error: Unable to generate request body");<br>
+            break;<br>
+        }<br>
+<br>
+        i_status = SendRequest (p_intf);<br>
+        if ( i_status )<br>
+        {<br>
+            msg_Warn (p_intf, "Error: Could not transmit request");<br>
+            b_wait = 1;<br>
+            continue;<br>
+        }<br>
+<br>
+        b_wait = 0;<br>
+    }<br>
+<br>
+    vlc_restorecancel (canc);<br>
+    return NULL;<br>
+}<br>
+<br>
-- <br>
2.20.1<br>
<br>
</blockquote></div>