[vlc-commits] [Git][videolan/vlc][3.0.x] 4 commits: DBus control: Extract method ProcessPlaylistChanged

Steve Lhomme (@robUx4) gitlab at videolan.org
Wed Jan 11 05:27:56 UTC 2023



Steve Lhomme pushed to branch 3.0.x at VideoLAN / VLC


Commits:
70c34c35 by Jorge Bellon-Castro at 2023-01-11T05:06:51+00:00
DBus control: Extract method ProcessPlaylistChanged

Extract switch body for track append and track remove events.
This simplifies adding independent logic to both cases.

- - - - -
0f6129e0 by Jorge Bellon-Castro at 2023-01-11T05:06:51+00:00
DBus control: do not query index of item if known

The track index is sometimes known when serialising a track's metadata.
This moves the index query outside the metadata serialisation function
when necessary.

- - - - -
7a1c58ac by Jorge Bellon-Castro at 2023-01-11T05:06:51+00:00
DBus control: add more TrackList signals

Implements the following signals for the
org.mpris.MediaPlayer2.TrackList interface:

- TrackAdded: a track was inserted to the list
- TrackRemoved: a track was removed from the list

- - - - -
4c12f2ea by Jorge Bellon at 2023-01-11T05:06:51+00:00
DBus control module: return DBus error message instead of entering an infinite loop

- - - - -


4 changed files:

- modules/control/dbus/dbus.c
- modules/control/dbus/dbus_player.c
- modules/control/dbus/dbus_tracklist.c
- modules/control/dbus/dbus_tracklist.h


Changes:

=====================================
modules/control/dbus/dbus.c
=====================================
@@ -92,6 +92,10 @@ static const DBusObjectPathVTable dbus_mpris_vtable = {
 typedef struct
 {
     int signal;
+    union {
+        tracklist_append_event_t *items_appended;
+        tracklist_remove_event_t *items_removed;
+    };
 } callback_info_t;
 
 enum
@@ -518,6 +522,34 @@ static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
     return i_fds;
 }
 
+
+/**
+ * ProcessPlaylistChanged() reacts to tracks being either inserted or removed from the playlist
+ *
+ * This function must be called by ProcessEvents only
+ *
+ * @param intf_thread_t *p_intf This interface thread state
+ * @param callback_info_t *p_events the list of events to process
+ */
+static void ProcessPlaylistChanged( intf_thread_t *p_intf,
+                                    vlc_dictionary_t *player_properties,
+                                    vlc_dictionary_t *tracklist_properties )
+{
+    playlist_t *playlist = p_intf->p_sys->p_playlist;
+    playlist_Lock(playlist);
+    bool b_can_play = !playlist_IsEmpty(playlist);
+    playlist_Unlock(playlist);
+
+    if( b_can_play != p_intf->p_sys->b_can_play )
+    {
+        p_intf->p_sys->b_can_play = b_can_play;
+        vlc_dictionary_insert( player_properties, "CanPlay", NULL );
+    }
+
+    if( !vlc_dictionary_has_key( tracklist_properties, "Tracks" ) )
+        vlc_dictionary_insert( tracklist_properties, "Tracks", NULL );
+}
+
 /**
  * ProcessEvents() reacts to a list of events originating from other VLC threads
  *
@@ -529,13 +561,19 @@ static int GetPollFds( intf_thread_t *p_intf, struct pollfd *p_fds )
 static void ProcessEvents( intf_thread_t *p_intf,
                            callback_info_t **p_events, int i_events )
 {
-    bool b_can_play = p_intf->p_sys->b_can_play;
-
     vlc_dictionary_t player_properties, tracklist_properties, root_properties;
     vlc_dictionary_init( &player_properties,    0 );
     vlc_dictionary_init( &tracklist_properties, 0 );
     vlc_dictionary_init( &root_properties,      0 );
 
+    // In case multiple *_ITEM_APPEND or *_ITEM_DELETED events appear on the
+    // list, the elements in their respective map values will be linked in
+    // order.
+    // We keep the tail of the list in order to append the elements to the end
+    // of each list.
+    tracklist_append_event_t *last_append = NULL;
+    tracklist_remove_event_t *last_remove = NULL;
+
     for( int i = 0; i < i_events; i++ )
     {
         switch( p_events[i]->signal )
@@ -582,23 +620,29 @@ static void ProcessEvents( intf_thread_t *p_intf,
             break;
         }
         case SIGNAL_PLAYLIST_ITEM_APPEND:
+            if( !last_append ) {
+                assert (!vlc_dictionary_has_key( &tracklist_properties, "TrackAdded" ) );
+                vlc_dictionary_insert( &tracklist_properties, "TrackAdded", p_events[i]->items_appended );
+
+                last_append = p_events[i]->items_appended;
+            } else {
+                last_append->change_ev.next = &p_events[i]->items_appended->change_ev;
+                last_append = p_events[i]->items_appended;
+            }
+            ProcessPlaylistChanged( p_intf, &player_properties, &tracklist_properties );
+            break;
         case SIGNAL_PLAYLIST_ITEM_DELETED:
-        {
-            playlist_t *p_playlist = p_intf->p_sys->p_playlist;
-            PL_LOCK;
-            b_can_play = playlist_CurrentSize( p_playlist ) > 0;
-            PL_UNLOCK;
-
-            if( b_can_play != p_intf->p_sys->b_can_play )
-            {
-                p_intf->p_sys->b_can_play = b_can_play;
-                vlc_dictionary_insert( &player_properties, "CanPlay", NULL );
+            if( !last_remove ) {
+                assert (!vlc_dictionary_has_key( &tracklist_properties, "TrackRemoved" ) );
+                vlc_dictionary_insert( &tracklist_properties, "TrackRemoved", p_events[i]->items_removed );
+
+                last_remove = p_events[i]->items_removed;
+            } else {
+                last_remove->change_ev.next = &p_events[i]->items_removed->change_ev;
+                last_remove = p_events[i]->items_removed;
             }
-
-            if( !vlc_dictionary_has_key( &tracklist_properties, "Tracks" ) )
-                vlc_dictionary_insert( &tracklist_properties, "Tracks", NULL );
+            ProcessPlaylistChanged( p_intf, &player_properties, &tracklist_properties );
             break;
-        }
         case SIGNAL_VOLUME_MUTED:
         case SIGNAL_VOLUME_CHANGE:
             vlc_dictionary_insert( &player_properties, "Volume", NULL );
@@ -1058,9 +1102,21 @@ static int AllCallback( vlc_object_t *p_this, const char *psz_var,
             info.signal = SIGNAL_VOLUME_MUTED;
     }
     else if( !strcmp( "playlist-item-append", psz_var ) )
-        info.signal = SIGNAL_PLAYLIST_ITEM_APPEND;
+    {
+        playlist_item_t *items[] = {newval.p_address};
+        info = (callback_info_t){
+            .signal = SIGNAL_PLAYLIST_ITEM_APPEND,
+            .items_appended = tracklist_append_event_create(items[0]->i_id, items, 1)
+        };
+    }
     else if( !strcmp( "playlist-item-deleted", psz_var ) )
-        info.signal = SIGNAL_PLAYLIST_ITEM_DELETED;
+    {
+        playlist_item_t *item = newval.p_address;
+        info = (callback_info_t){
+            .signal = SIGNAL_PLAYLIST_ITEM_DELETED,
+            .items_removed = tracklist_remove_event_create(item->i_id, 1)
+        };
+    }
     else if( !strcmp( "random", psz_var ) )
         info.signal = SIGNAL_RANDOM;
     else if( !strcmp( "fullscreen", psz_var ) )


=====================================
modules/control/dbus/dbus_player.c
=====================================
@@ -514,6 +514,7 @@ MarshalMetadata( intf_thread_t *p_intf, DBusMessageIter *container )
     item = playlist_CurrentPlayingItem( playlist );
 
     if( item != NULL )
+        // TODO: vlc_object_hold this
         result = GetInputMeta( item, container );
     else
     {   // avoid breaking the type marshalling


=====================================
modules/control/dbus/dbus_tracklist.c
=====================================
@@ -39,6 +39,69 @@
 #include "dbus_tracklist.h"
 #include "dbus_common.h"
 
+
+tracklist_append_event_t *tracklist_append_event_create(size_t index, playlist_item_t *const items[], size_t count) {
+    tracklist_append_event_t* result = malloc(sizeof(tracklist_append_event_t) + sizeof(playlist_item_t[count]));
+    if (!result)
+        return result;
+
+    *result = (tracklist_append_event_t) { .change_ev = { .index = index, .count = count } };
+    for (size_t i = 0; i < count; ++i) {
+        // We can't hold a playlist_item_t, so we copy it and hold onto its input
+        result->items[i] = *items[i];
+        input_item_Hold(items[i]->p_input);
+    }
+    return result;
+}
+
+tracklist_remove_event_t *tracklist_remove_event_create(size_t index, size_t count) {
+    tracklist_remove_event_t* result = malloc(sizeof(tracklist_remove_event_t));
+    if (!result)
+        return result;
+
+    *result = (tracklist_remove_event_t) { .change_ev = { .index = index, .count = count } };
+    return result;
+}
+
+void tracklist_append_event_destroy(tracklist_append_event_t *event) {
+    if (!event)
+        return;
+    for (size_t i = 0; i < event->change_ev.count; ++i) {
+        // Release referenced input
+        input_item_Release(event->items[i].p_input);
+    }
+    free(event);
+}
+
+void tracklist_remove_event_destroy(tracklist_remove_event_t *event) {
+    free(event);
+}
+
+static DBusHandlerResult InvalidTrackId(DBusConnection *p_conn,
+                                        DBusMessage *p_from,
+                                        const char *trackId,
+                                        void *p_this) {
+  msg_Err((vlc_object_t *)p_this, "Invalid track id: %s", trackId);
+
+  DBusMessage *p_msg = dbus_message_new_error_printf(
+      p_from, DBUS_ERROR_UNKNOWN_OBJECT, "Invalid track id: %s", trackId);
+  if (!p_msg)
+    return DBUS_HANDLER_RESULT_NEED_MEMORY;
+  REPLY_SEND;
+}
+
+static DBusHandlerResult InvalidArguments(DBusConnection *p_conn,
+                                          DBusMessage *p_from,
+                                          void *p_this) {
+  msg_Err((vlc_object_t *)p_this, "Invalid arguments");
+
+  DBusMessage *p_msg = dbus_message_new_error(p_from, DBUS_ERROR_INVALID_ARGS,
+                                              "Invalid arguments");
+  if (!p_msg)
+    return DBUS_HANDLER_RESULT_NEED_MEMORY;
+  REPLY_SEND;
+}
+
 DBUS_METHOD( AddTrack )
 {
     REPLY_INIT;
@@ -135,41 +198,48 @@ DBUS_METHOD( GetTracksMetadata )
 
     if( DBUS_TYPE_ARRAY != dbus_message_iter_get_arg_type( &in_args ) )
     {
-        msg_Err( (vlc_object_t*) p_this, "Invalid arguments" );
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        return InvalidArguments(p_conn, p_from, p_this);
     }
 
     dbus_message_iter_recurse( &in_args, &track_ids );
     dbus_message_iter_open_container( &args, DBUS_TYPE_ARRAY, "a{sv}", &meta );
 
-    while( DBUS_TYPE_OBJECT_PATH ==
-           dbus_message_iter_get_arg_type( &track_ids ) )
+    bool id_valid = true;
+    while( DBUS_TYPE_OBJECT_PATH == dbus_message_iter_get_arg_type( &track_ids ) )
     {
         dbus_message_iter_get_basic( &track_ids, &psz_track_id );
 
         if( 1 != sscanf( psz_track_id, MPRIS_TRACKID_FORMAT, &i_track_id ) )
         {
-            msg_Err( (vlc_object_t*) p_this, "Invalid track id: %s",
-                                             psz_track_id );
-            continue;
+            id_valid = false;
+            break;
         }
 
-        PL_LOCK;
-        for( int i = 0; i < p_playlist->current.i_size; i++ )
+        playlist_item_t *item = NULL;
+        playlist_Lock(p_playlist);
+        item = playlist_ItemGetById(p_playlist, i_track_id);
+        if (item)
         {
-            playlist_item_t *item = p_playlist->current.p_elems[i];
+            GetInputMeta(item, &meta);
+        }
+        playlist_Unlock(p_playlist);
 
-            if( item->i_id == i_track_id )
-            {
-                GetInputMeta( item, &meta );
-                break;
-            }
+        if (!item)
+        {
+            id_valid = false;
+            break;
         }
-        PL_UNLOCK;
 
         dbus_message_iter_next( &track_ids );
     }
 
+    if( !id_valid )
+    {
+        dbus_message_iter_abandon_container( &args, &meta );
+        dbus_message_unref(p_msg);
+        return InvalidTrackId(p_conn, p_from, psz_track_id, p_this);
+    }
+
     dbus_message_iter_close_container( &args, &meta );
     REPLY_SEND;
 }
@@ -198,26 +268,21 @@ DBUS_METHOD( GoTo )
     }
 
     if( 1 != sscanf( psz_track_id, MPRIS_TRACKID_FORMAT, &i_track_id ) )
-    {
-        msg_Err( (vlc_object_t*) p_this, "Invalid track id %s", psz_track_id );
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    }
+        goto invalid_track_id;
 
     PL_LOCK;
+    playlist_item_t *item = playlist_ItemGetById( p_playlist, i_track_id );
+    if( item )
+        playlist_ViewPlay( p_playlist, item->p_parent, item );
+    PL_UNLOCK;
 
-    for( int i = 0; i < p_playlist->current.i_size; i++ )
-    {
-        playlist_item_t *item = p_playlist->current.p_elems[i];
+    if( !item )
+        goto invalid_track_id;
 
-        if( item->i_id == i_track_id )
-        {
-            playlist_ViewPlay( p_playlist, item->p_parent, item );
-            break;
-        }
-    }
-
-    PL_UNLOCK;
     REPLY_SEND;
+
+invalid_track_id:
+    return InvalidTrackId(p_conn, p_from, psz_track_id, p_this);
 }
 
 DBUS_METHOD( RemoveTrack )
@@ -244,60 +309,66 @@ DBUS_METHOD( RemoveTrack )
     }
 
     if( 1 != sscanf( psz_id, MPRIS_TRACKID_FORMAT, &i_id ) )
-    {
-        msg_Err( (vlc_object_t*) p_this, "Invalid track id: %s", psz_id );
-        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-    }
+        goto invalid_track_id;
 
     PL_LOCK;
+    playlist_item_t *item = playlist_ItemGetById( p_playlist, i_id );
+    if( item )
+        playlist_NodeDelete( p_playlist, item );
+    PL_UNLOCK;
 
-    for( int i = 0; i < p_playlist->current.i_size; i++ )
-    {
-        playlist_item_t *item = p_playlist->current.p_elems[i];
-
-        if( item->i_id == i_id )
-        {
-            playlist_NodeDelete( p_playlist, item );
-            break;
-        }
-    }
+    if( !item )
+        goto invalid_track_id;
 
-    PL_UNLOCK;
     REPLY_SEND;
+
+invalid_track_id:
+    return InvalidTrackId(p_conn, p_from, psz_id, p_this);
+}
+
+static int MarshalTrack( DBusMessageIter *iter, int id )
+{
+    char *psz_track_id = NULL;
+    int ret = VLC_SUCCESS;
+
+    if (asprintf(&psz_track_id, MPRIS_TRACKID_FORMAT, id) == -1)
+        ret = VLC_ENOMEM;
+
+    if (ret == VLC_SUCCESS &&
+        !dbus_message_iter_append_basic( iter,
+                                         DBUS_TYPE_OBJECT_PATH,
+                                         &psz_track_id ) )
+    {
+       ret = VLC_ENOMEM;
+    }
+    free( psz_track_id );
+    return ret;
 }
 
 static int
 MarshalTracks( intf_thread_t *p_intf, DBusMessageIter *container )
 {
     DBusMessageIter tracks;
-    char         *psz_track_id = NULL;
-    playlist_t   *p_playlist   = p_intf->p_sys->p_playlist;
+    playlist_t *p_playlist = p_intf->p_sys->p_playlist;
 
     dbus_message_iter_open_container( container, DBUS_TYPE_ARRAY, "o",
                                       &tracks );
 
-    PL_LOCK;
+    playlist_Lock(p_playlist);
 
     for( int i = 0; i < p_playlist->current.i_size; i++ )
     {
         playlist_item_t *item = p_playlist->current.p_elems[i];
-
-        if( ( -1 == asprintf( &psz_track_id,
-                              MPRIS_TRACKID_FORMAT,
-                              item->i_id ) ) ||
-            !dbus_message_iter_append_basic( &tracks,
-                                             DBUS_TYPE_OBJECT_PATH,
-                                             &psz_track_id ) )
+        int err = MarshalTrack( &tracks, item->i_id );
+        if (err !=  VLC_SUCCESS)
         {
-            PL_UNLOCK;
+            playlist_Unlock(p_playlist);
             dbus_message_iter_abandon_container( container, &tracks );
-            return VLC_ENOMEM;
+            return err;
         }
-
-        free( psz_track_id );
     }
 
-    PL_UNLOCK;
+    playlist_Unlock(p_playlist);
 
     if( !dbus_message_iter_close_container( container, &tracks ) )
         return VLC_ENOMEM;
@@ -499,8 +570,66 @@ PropertiesChangedSignal( intf_thread_t    *p_intf,
 }
 
 /**
- * TrackListPropertiesChangedEmit: Emits the
- * org.freedesktop.DBus.Properties.PropertiesChanged signal
+ * TrackAddedSignal: synthetizes and sends the
+ * org.mpris.MediaPlayer2.TrackList.TrackAdded signal
+ */
+static DBusHandlerResult
+TrackAddedSignal( intf_thread_t    *p_intf,
+                  size_t index,
+                  playlist_item_t *item )
+{
+    (void) index; // unused
+
+    DBusConnection  *p_conn = p_intf->p_sys->p_conn;
+    DBusMessageIter meta;
+
+    SIGNAL_INIT( "MediaPlayer2.TrackList",
+                 DBUS_MPRIS_OBJECT_PATH,
+                 "TrackAdded" );
+
+    OUT_ARGUMENTS;
+
+    if( unlikely(!dbus_message_iter_open_container( &args,
+                                                    DBUS_TYPE_ARRAY, "a{sv}",
+                                                    &meta )) )
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+    GetInputMeta(item, &meta);
+
+    if( unlikely(!dbus_message_iter_close_container( &args,
+                                                     &meta )) )
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+    if ( MarshalTrack( &args, item->i_id ) !=  VLC_SUCCESS )
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+    SIGNAL_SEND;
+}
+
+/**
+ * TrackRemovedSignal: synthetizes and sends the
+ * org.mpris.MediaPlayer2.TrackList.TrackRemoved signal
+ */
+static DBusHandlerResult
+TrackRemovedSignal( intf_thread_t *p_intf, size_t index )
+{
+    DBusConnection  *p_conn = p_intf->p_sys->p_conn;
+
+    SIGNAL_INIT( "MediaPlayer2.TrackList",
+                 DBUS_MPRIS_OBJECT_PATH,
+                 "TrackRemoved" );
+
+    OUT_ARGUMENTS;
+
+    if ( MarshalTrack( &args, index ) !=  VLC_SUCCESS )
+        return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+    SIGNAL_SEND;
+}
+/**
+ * TrackListPropertiesChangedEmit: Emits the following signals:
+ * - org.freedesktop.DBus.Properties.PropertiesChanged
+ * - org.mpris.MediaPlayer2.TrackList.TrackAdded
  */
 int TrackListPropertiesChangedEmit( intf_thread_t    * p_intf,
                                     vlc_dictionary_t * p_changed_properties )
@@ -509,5 +638,35 @@ int TrackListPropertiesChangedEmit( intf_thread_t    * p_intf,
         return VLC_SUCCESS;
 
     PropertiesChangedSignal( p_intf, p_changed_properties );
+
+    if( vlc_dictionary_has_key( p_changed_properties, "TrackAdded" ) ) {
+        tracklist_append_event_t *added_tracks =
+            vlc_dictionary_value_for_key( p_changed_properties, "TrackAdded" );
+
+        while (added_tracks) {
+            for (size_t i = 0; i < added_tracks->change_ev.count; ++i) {
+                TrackAddedSignal( p_intf,
+                        added_tracks->change_ev.index + i,
+                        &added_tracks->items[i] );
+            }
+            added_tracks = tracklist_append_event_next(added_tracks);
+        }
+        tracklist_append_event_destroy( added_tracks );
+    }
+
+    if( vlc_dictionary_has_key( p_changed_properties, "TrackRemoved" ) ) {
+        tracklist_remove_event_t *removed_tracks =
+            vlc_dictionary_value_for_key( p_changed_properties, "TrackRemoved" );
+
+        while (removed_tracks) {
+            for (size_t i = 0; i < removed_tracks->change_ev.count; ++i) {
+                TrackRemovedSignal( p_intf, removed_tracks->change_ev.index + i );
+            }
+            removed_tracks = tracklist_remove_event_next(removed_tracks);
+        }
+
+        tracklist_remove_event_destroy( removed_tracks );
+    }
+
     return VLC_SUCCESS;
 }


=====================================
modules/control/dbus/dbus_tracklist.h
=====================================
@@ -29,6 +29,7 @@
 
 #include <vlc_common.h>
 #include <vlc_interface.h>
+#include <vlc_playlist.h>
 #include "dbus_common.h"
 
 #define DBUS_MPRIS_TRACKLIST_INTERFACE    "org.mpris.MediaPlayer2.TrackList"
@@ -37,6 +38,61 @@
 #define DBUS_MPRIS_NOTRACK   "/org/mpris/MediaPlayer2/TrackList/NoTrack"
 #define DBUS_MPRIS_APPEND    "/org/mpris/MediaPlayer2/TrackList/Append"
 
+struct tracklist_change_event {
+    size_t index;
+    size_t count;
+    struct tracklist_change_event *next;
+};
+
+struct tracklist_append_event {
+    struct tracklist_change_event change_ev;
+    playlist_item_t items[];
+};
+
+struct tracklist_remove_event {
+    struct tracklist_change_event change_ev;
+};
+
+typedef struct tracklist_append_event tracklist_append_event_t;
+typedef struct tracklist_remove_event tracklist_remove_event_t;
+
+/* Creates an event holding what items have been appended to a tracklist.
+ * The event data will be used to generate TrackAdded DBus signals later on.
+ */
+tracklist_append_event_t *
+tracklist_append_event_create( size_t index,
+                               playlist_item_t *const items[],
+                               size_t count );
+
+/* Creates an event holding what items have been removed from a tracklist.
+ *  The event data will be used to generate TrackRemoved DBus signals later on.
+ */
+tracklist_remove_event_t *
+tracklist_remove_event_create( size_t index, size_t count );
+
+/* Releases any resources reserved for this event */
+void tracklist_append_event_destroy( tracklist_append_event_t *event );
+void tracklist_remove_event_destroy( tracklist_remove_event_t *event );
+
+/* Gets next event in the list */
+static inline tracklist_append_event_t *
+tracklist_append_event_next( tracklist_append_event_t *event ) {
+    if( !event )
+        return NULL;
+    char *p = (char *) event->change_ev.next;
+    return (tracklist_append_event_t *)
+        (p - offsetof(struct tracklist_append_event, change_ev));
+}
+
+static inline tracklist_remove_event_t *
+tracklist_remove_event_next( tracklist_remove_event_t *event ) {
+    if( !event )
+        return NULL;
+    char *p = (char *) event->change_ev.next;
+    return (tracklist_remove_event_t *)
+        (p - offsetof(struct tracklist_remove_event, change_ev));
+}
+
 /* Handle incoming dbus messages */
 DBusHandlerResult handle_tracklist ( DBusConnection *p_conn,
                                      DBusMessage *p_from,



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/18a5a49a6d9d0a2f9e5890faca567cb805417f1f...4c12f2eacbb094929e5066e9be285f9eded50f35

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/18a5a49a6d9d0a2f9e5890faca567cb805417f1f...4c12f2eacbb094929e5066e9be285f9eded50f35
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list